@canonry/canonry 4.136.2 → 4.138.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 (30) hide show
  1. package/README.md +2 -1
  2. package/assets/assets/{AuditHistoryPanel-DtUGiN21.js → AuditHistoryPanel-CIdoSKjP.js} +1 -1
  3. package/assets/assets/{BacklinksPage-BGwtmwfz.js → BacklinksPage-BYnzQLFq.js} +1 -1
  4. package/assets/assets/{ChartPrimitives-Df2UC1g3.js → ChartPrimitives-bx4p5MDx.js} +1 -1
  5. package/assets/assets/{HistoryPage-AtaMVEUx.js → HistoryPage-CjLb8TKY.js} +1 -1
  6. package/assets/assets/ProjectPage-C_bSGiLs.js +8 -0
  7. package/assets/assets/{RunRow-Cmf-5xVo.js → RunRow-D6W6D1BX.js} +1 -1
  8. package/assets/assets/{RunsPage-BAPy9Yri.js → RunsPage-CQT4lXFp.js} +1 -1
  9. package/assets/assets/{SettingsPage-BXO7WpTo.js → SettingsPage-DVQKzz6s.js} +1 -1
  10. package/assets/assets/{TrafficPage-IH-wiPu2.js → TrafficPage-BdocLgWw.js} +1 -1
  11. package/assets/assets/{TrafficSourceDetailPage-CTTN-P52.js → TrafficSourceDetailPage-CTZF_R9C.js} +1 -1
  12. package/assets/assets/{arrow-left-BwzAIeJ_.js → arrow-left-ClC65OiR.js} +1 -1
  13. package/assets/assets/extract-error-message-CsgTjzyj.js +1 -0
  14. package/assets/assets/index-BZgknVrZ.js +213 -0
  15. package/assets/assets/{index-CglduHCN.css → index-DY7GziwY.css} +1 -1
  16. package/assets/assets/{trash-2-C50OeBAV.js → trash-2-nr8RRH47.js} +1 -1
  17. package/assets/index.html +2 -2
  18. package/dist/{chunk-MXR3GI5L.js → chunk-3KGJ7BXW.js} +234 -16
  19. package/dist/{chunk-YVMQWVNY.js → chunk-GO24URVP.js} +187 -15
  20. package/dist/{chunk-SMFUCXHI.js → chunk-R3GAWKHM.js} +1 -1
  21. package/dist/{chunk-GGIRLRAY.js → chunk-RJ7OW6W6.js} +3076 -2904
  22. package/dist/cli.js +147 -33
  23. package/dist/index.d.ts +2 -0
  24. package/dist/index.js +4 -4
  25. package/dist/{intelligence-service-BD6BHTEJ.js → intelligence-service-5RJHJLYA.js} +2 -2
  26. package/dist/mcp.js +2 -2
  27. package/package.json +11 -11
  28. package/assets/assets/ProjectPage-OlBjxXNx.js +0 -8
  29. package/assets/assets/extract-error-message-BIcIoJLN.js +0 -1
  30. 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-BZgknVrZ.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,12 +12,12 @@
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-BZgknVrZ.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">
19
19
  <link rel="modulepreload" crossorigin href="./assets/vendor-markdown-6dwjPOKK.js">
20
- <link rel="stylesheet" crossorigin href="./assets/index-CglduHCN.css">
20
+ <link rel="stylesheet" crossorigin href="./assets/index-DY7GziwY.css">
21
21
  </head>
22
22
  <body>
23
23
  <div id="root"></div>
@@ -10,7 +10,7 @@ import {
10
10
  loadConfig,
11
11
  loadConfigRaw,
12
12
  saveConfigPatch
13
- } from "./chunk-SMFUCXHI.js";
13
+ } from "./chunk-R3GAWKHM.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-GO24URVP.js";
123
123
  import {
124
124
  AGENT_MEMORY_VALUE_MAX_BYTES,
125
125
  AGENT_PROVIDER_IDS,
@@ -129,13 +129,16 @@ import {
129
129
  AdsCampaignBiddingTypes,
130
130
  AgentProviderIds,
131
131
  BacklinkSources,
132
+ CITED_URL_CAPTURE_VERSION,
132
133
  CcReleaseSyncStatuses,
133
134
  CodingAgents,
134
135
  DiscoveryCompetitorTypes,
135
136
  LLM_CAPABILITIES,
136
137
  LlmCapabilities,
137
138
  MemorySources,
139
+ ONBOARDING_FLOW_VERSION,
138
140
  ProviderNames,
141
+ ROUTE_CAPABLE_CITED_URL_PROVIDERS,
139
142
  ResearchQueryStatuses,
140
143
  ResearchRunStatuses,
141
144
  RunKinds,
@@ -159,18 +162,21 @@ import {
159
162
  agentMemoryUpsertRequestSchema,
160
163
  authInvalid,
161
164
  authRequired,
165
+ bucketOnboardingCount,
162
166
  buildRunErrorFromMessages,
163
167
  classifySkillFile,
164
168
  coerceSkillManifest,
165
169
  computeBacklinkSummaryMetrics,
166
170
  contentActionLabel,
167
171
  contentBriefDtoSchema,
172
+ deriveCitedUrlCandidates,
168
173
  determineAnswerMentioned,
169
174
  dollarsToMicros,
170
175
  effectiveBrandNames,
171
176
  effectiveDomains,
172
177
  embedClientConfigForRequest,
173
178
  factorStatusFromScore,
179
+ filterCapturedCitedUrls,
174
180
  formatIsoDateInTimeZone,
175
181
  frameAncestorsHeaderValue,
176
182
  hostMatchesAnyDomain,
@@ -180,6 +186,7 @@ import {
180
186
  isBrowserProvider,
181
187
  isGhostTelemetryEvent,
182
188
  isRetryableHttpError,
189
+ isVertexGroundingRedirect,
183
190
  mapWithConcurrency,
184
191
  normalizeIdTokens,
185
192
  normalizeServedModel,
@@ -199,7 +206,7 @@ import {
199
206
  validationError,
200
207
  winnabilityClassLabel,
201
208
  withRetry
202
- } from "./chunk-GGIRLRAY.js";
209
+ } from "./chunk-RJ7OW6W6.js";
203
210
 
204
211
  // src/telemetry.ts
205
212
  import crypto from "crypto";
@@ -214,8 +221,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
221
  function shouldDropTelemetryEvent(event, properties) {
215
222
  return isGhostTelemetryEvent(event, properties);
216
223
  }
224
+ function bucketCliCommandDuration(durationMs) {
225
+ const duration = Number.isFinite(durationMs) ? Math.max(0, durationMs) : 0;
226
+ if (duration < 1e3) return "under_1s";
227
+ if (duration < 1e4) return "1s_to_10s";
228
+ if (duration < 6e4) return "10s_to_1m";
229
+ if (duration < 3e5) return "1m_to_5m";
230
+ if (duration < 18e5) return "5m_to_30m";
231
+ return "30m_or_more";
232
+ }
233
+ function trackCliCommandFinished(input) {
234
+ trackEvent(
235
+ "cli.command.finished",
236
+ {
237
+ command: input.command,
238
+ success: input.success,
239
+ duration_bucket: bucketCliCommandDuration(input.durationMs),
240
+ ...input.setupState ? { setup_state: input.setupState } : {}
241
+ },
242
+ input.errorCode ? { errorCode: input.errorCode } : void 0
243
+ );
244
+ }
217
245
  var SESSION_ID = crypto.randomUUID();
218
246
  var CURRENT_SOURCE = "cli";
247
+ var TELEMETRY_SOURCE_VALUES = /* @__PURE__ */ new Set([
248
+ "cli",
249
+ "cli-server",
250
+ "api",
251
+ "mcp-server",
252
+ "wp-plugin",
253
+ "dashboard",
254
+ "agent-runtime"
255
+ ]);
256
+ function envSourceOverride() {
257
+ const raw = process.env.CANONRY_TELEMETRY_SOURCE?.trim();
258
+ return raw && TELEMETRY_SOURCE_VALUES.has(raw) ? raw : void 0;
259
+ }
219
260
  function setTelemetrySource(source) {
220
261
  CURRENT_SOURCE = source;
221
262
  }
@@ -331,9 +372,10 @@ function trackEvent(event, properties, options) {
331
372
  const anonymousId = getOrCreateAnonymousId();
332
373
  if (!anonymousId) return;
333
374
  const payload = {
375
+ eventId: options?.eventId ?? crypto.randomUUID(),
334
376
  anonymousId,
335
377
  sessionId: SESSION_ID,
336
- source: options?.source ?? CURRENT_SOURCE,
378
+ source: options?.source ?? envSourceOverride() ?? CURRENT_SOURCE,
337
379
  event,
338
380
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
339
381
  version: VERSION,
@@ -347,13 +389,17 @@ function trackEvent(event, properties, options) {
347
389
  const controller = new AbortController();
348
390
  const timeout = setTimeout(() => controller.abort(), TIMEOUT_MS);
349
391
  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));
392
+ try {
393
+ void fetch(TELEMETRY_ENDPOINT, {
394
+ method: "POST",
395
+ headers: { "Content-Type": "application/json" },
396
+ body: JSON.stringify(payload),
397
+ signal: controller.signal
398
+ }).catch(() => {
399
+ }).finally(() => clearTimeout(timeout));
400
+ } catch {
401
+ clearTimeout(timeout);
402
+ }
357
403
  }
358
404
 
359
405
  // src/update-check.ts
@@ -3071,7 +3117,7 @@ import crypto4 from "crypto";
3071
3117
  import fs2 from "fs";
3072
3118
  import path3 from "path";
3073
3119
  import os3 from "os";
3074
- import { and as and2, eq as eq2, inArray, sql as sql2 } from "drizzle-orm";
3120
+ import { and as and2, eq as eq2, inArray, ne, sql as sql2 } from "drizzle-orm";
3075
3121
 
3076
3122
  // src/run-telemetry.ts
3077
3123
  import crypto2 from "crypto";
@@ -3205,6 +3251,87 @@ function releaseDailyQueryQuota(db, input) {
3205
3251
  db.update(usageCounters).set({ count: sql`MAX(0, ${usageCounters.count} - ${input.count})`, updatedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(and(eq(usageCounters.scope, input.scope), eq(usageCounters.period, input.period), eq(usageCounters.metric, "queries"))).run();
3206
3252
  }
3207
3253
 
3254
+ // src/cited-url-capture.ts
3255
+ var RESOLUTION_CONCURRENCY = 5;
3256
+ var RESOLUTION_TIMEOUT_MS = 5e3;
3257
+ var MAX_VERTEX_PROXY_HOPS = 3;
3258
+ var REDIRECT_STATUSES = /* @__PURE__ */ new Set([301, 302, 303, 307, 308]);
3259
+ function isRouteCapableProvider(provider) {
3260
+ return ROUTE_CAPABLE_CITED_URL_PROVIDERS.includes(provider);
3261
+ }
3262
+ function parseHttpUrl(value, base) {
3263
+ try {
3264
+ const url = new URL(value, base);
3265
+ if (url.protocol !== "http:" && url.protocol !== "https:") return void 0;
3266
+ url.hash = "";
3267
+ return url;
3268
+ } catch {
3269
+ return void 0;
3270
+ }
3271
+ }
3272
+ async function resolveVertexGroundingRedirect(value, opts = {}) {
3273
+ const initial = parseHttpUrl(value);
3274
+ if (!initial || !isVertexGroundingRedirect(initial)) return void 0;
3275
+ const fetchImpl = opts.fetchImpl ?? globalThis.fetch;
3276
+ const timeoutMs = opts.timeoutMs ?? RESOLUTION_TIMEOUT_MS;
3277
+ let current = initial;
3278
+ for (let hop = 0; hop <= MAX_VERTEX_PROXY_HOPS; hop++) {
3279
+ const controller = new AbortController();
3280
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
3281
+ let response;
3282
+ try {
3283
+ response = await fetchImpl(current, { redirect: "manual", signal: controller.signal });
3284
+ } catch {
3285
+ return void 0;
3286
+ } finally {
3287
+ clearTimeout(timer);
3288
+ }
3289
+ if (!REDIRECT_STATUSES.has(response.status)) return void 0;
3290
+ const location = response.headers.get("location");
3291
+ if (!location) return void 0;
3292
+ const next = parseHttpUrl(location, current.toString());
3293
+ if (!next) return void 0;
3294
+ if (!isVertexGroundingRedirect(next)) return next.toString();
3295
+ current = next;
3296
+ }
3297
+ return void 0;
3298
+ }
3299
+ async function captureCitedUrls(provider, sources, opts = {}) {
3300
+ const sourceCount = sources.length;
3301
+ if (!isRouteCapableProvider(provider)) {
3302
+ return {
3303
+ citedUrls: null,
3304
+ captureStatus: "unsupported",
3305
+ sourceCount,
3306
+ resolvedCount: 0,
3307
+ captureVersion: CITED_URL_CAPTURE_VERSION
3308
+ };
3309
+ }
3310
+ const candidates = deriveCitedUrlCandidates(sources);
3311
+ const candidateCount = candidates.length;
3312
+ let resolved;
3313
+ try {
3314
+ resolved = await mapWithConcurrency(candidates, RESOLUTION_CONCURRENCY, async (candidate) => {
3315
+ const parsed = parseHttpUrl(candidate);
3316
+ if (!parsed) return void 0;
3317
+ if (!isVertexGroundingRedirect(parsed)) return candidate;
3318
+ return resolveVertexGroundingRedirect(candidate, opts);
3319
+ });
3320
+ } catch {
3321
+ resolved = candidates.map(() => void 0);
3322
+ }
3323
+ const acceptedPerSource = resolved.map((value) => filterCapturedCitedUrls([value]).at(0));
3324
+ const resolvedCount = acceptedPerSource.filter((value) => value !== void 0).length;
3325
+ const captureStatus = resolvedCount === candidateCount ? "complete" : resolvedCount > 0 ? "partial" : "failed";
3326
+ return {
3327
+ citedUrls: filterCapturedCitedUrls(acceptedPerSource),
3328
+ captureStatus,
3329
+ sourceCount,
3330
+ resolvedCount,
3331
+ captureVersion: CITED_URL_CAPTURE_VERSION
3332
+ };
3333
+ }
3334
+
3208
3335
  // src/job-runner.ts
3209
3336
  var log = createLogger("JobRunner");
3210
3337
  var RunCancelledError = class extends Error {
@@ -3278,10 +3405,18 @@ function classifyOneProviderError(message) {
3278
3405
  return "UNKNOWN";
3279
3406
  }
3280
3407
  var JobRunner = class {
3408
+ /**
3409
+ * Invoked exactly when `activation.completed` is emitted: the project's
3410
+ * first non-empty, non-probe answer-visibility result. The serve process
3411
+ * uses it to thank the operator once; it is a UX hook, not telemetry, so it
3412
+ * fires (and is given) independently of whether telemetry is enabled.
3413
+ */
3414
+ onFirstActivation;
3281
3415
  db;
3282
3416
  registry;
3283
3417
  onRunCompleted;
3284
- constructor(db, registry) {
3418
+ constructor(db, registry, opts) {
3419
+ this.onFirstActivation = opts?.onFirstActivation;
3285
3420
  this.db = db;
3286
3421
  this.registry = registry;
3287
3422
  }
@@ -3420,7 +3555,31 @@ var JobRunner = class {
3420
3555
  config
3421
3556
  );
3422
3557
  this.throwIfRunCancelled(runId);
3423
- const normalized = adapter.normalizeResult(raw);
3558
+ const providerResult = adapter.normalizeResult(raw);
3559
+ const rawGroundingSources = providerResult.groundingSources;
3560
+ const normalized = {
3561
+ ...providerResult,
3562
+ groundingSources: Array.isArray(rawGroundingSources) ? rawGroundingSources : []
3563
+ };
3564
+ let citedUrlCapture;
3565
+ try {
3566
+ citedUrlCapture = await captureCitedUrls(providerName, rawGroundingSources);
3567
+ } catch (err) {
3568
+ citedUrlCapture = {
3569
+ citedUrls: [],
3570
+ captureStatus: "failed",
3571
+ sourceCount: normalized.groundingSources.length,
3572
+ resolvedCount: 0,
3573
+ captureVersion: CITED_URL_CAPTURE_VERSION
3574
+ };
3575
+ log.warn("query.cited-url-capture-failed", {
3576
+ runId,
3577
+ provider: providerName,
3578
+ query: q.query,
3579
+ error: err instanceof Error ? err.message : String(err)
3580
+ });
3581
+ }
3582
+ this.throwIfRunCancelled(runId);
3424
3583
  log.info("query.result", { runId, provider: providerName, query: q.query, citedDomains: normalized.citedDomains, groundingSources: normalized.groundingSources.map((s) => s.uri), matchDomains: allDomains });
3425
3584
  const citationState = determineCitationState(normalized, allDomains);
3426
3585
  const answerMentioned = determineAnswerMentioned(
@@ -3456,6 +3615,11 @@ var JobRunner = class {
3456
3615
  answerMentioned,
3457
3616
  answerText: normalized.answerText,
3458
3617
  citedDomains: normalized.citedDomains,
3618
+ citedUrls: citedUrlCapture.citedUrls,
3619
+ captureStatus: citedUrlCapture.captureStatus,
3620
+ sourceCount: citedUrlCapture.sourceCount,
3621
+ resolvedCount: citedUrlCapture.resolvedCount,
3622
+ captureVersion: citedUrlCapture.captureVersion,
3459
3623
  competitorOverlap: overlap,
3460
3624
  recommendedCompetitors: extractedCompetitors,
3461
3625
  location: runLocation?.label ?? null,
@@ -3482,6 +3646,11 @@ var JobRunner = class {
3482
3646
  answerMentioned,
3483
3647
  answerText: normalized.answerText,
3484
3648
  citedDomains: normalized.citedDomains,
3649
+ citedUrls: citedUrlCapture.citedUrls,
3650
+ captureStatus: citedUrlCapture.captureStatus,
3651
+ sourceCount: citedUrlCapture.sourceCount,
3652
+ resolvedCount: citedUrlCapture.resolvedCount,
3653
+ captureVersion: citedUrlCapture.captureVersion,
3485
3654
  competitorOverlap: overlap,
3486
3655
  recommendedCompetitors: extractedCompetitors,
3487
3656
  location: runLocation?.label ?? null,
@@ -3553,6 +3722,19 @@ var JobRunner = class {
3553
3722
  }),
3554
3723
  failureCode ? { errorCode: failureCode } : void 0
3555
3724
  );
3725
+ if (existingRun.kind === "answer-visibility" && runTrigger !== "probe" && totalSnapshotsInserted > 0 && !this.hasPriorActivation(projectId, runId)) {
3726
+ trackEvent("activation.completed", {
3727
+ flowVersion: ONBOARDING_FLOW_VERSION,
3728
+ status: finalStatus,
3729
+ providerCountBucket: bucketOnboardingCount(executionContext.providerCount),
3730
+ queryCountBucket: bucketOnboardingCount(executionContext.queryCount),
3731
+ snapshotCountBucket: bucketOnboardingCount(totalSnapshotsInserted)
3732
+ });
3733
+ try {
3734
+ this.onFirstActivation?.();
3735
+ } catch {
3736
+ }
3737
+ }
3556
3738
  this.incrementUsage(projectId, "runs", 1);
3557
3739
  if (this.onRunCompleted) {
3558
3740
  this.onRunCompleted(runId, projectId).catch((err) => {
@@ -3641,8 +3823,18 @@ var JobRunner = class {
3641
3823
  }
3642
3824
  providerReservations.clear();
3643
3825
  }
3826
+ hasPriorActivation(projectId, currentRunId) {
3827
+ return this.db.select({ id: querySnapshots.id }).from(querySnapshots).innerJoin(runs, eq2(querySnapshots.runId, runs.id)).where(and2(
3828
+ eq2(runs.projectId, projectId),
3829
+ eq2(runs.kind, "answer-visibility"),
3830
+ inArray(runs.status, ["completed", "partial"]),
3831
+ ne(runs.trigger, "probe"),
3832
+ ne(runs.id, currentRunId)
3833
+ )).limit(1).get() !== void 0;
3834
+ }
3644
3835
  getRunState(runId) {
3645
3836
  return this.db.select({
3837
+ kind: runs.kind,
3646
3838
  status: runs.status,
3647
3839
  finishedAt: runs.finishedAt,
3648
3840
  error: runs.error,
@@ -3697,6 +3889,21 @@ function buildPhases(input) {
3697
3889
  return { setup_ms, provider_call_ms, total_ms };
3698
3890
  }
3699
3891
 
3892
+ // src/activation-notice.ts
3893
+ 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";
3894
+ function maybeShowActivationNotice(io = {}) {
3895
+ const isTTY = io.isTTY ?? Boolean(process.stderr.isTTY);
3896
+ const write = io.write ?? ((line) => process.stderr.write(line));
3897
+ try {
3898
+ if (!isTTY) return;
3899
+ const raw = loadConfigRaw();
3900
+ if (!raw || raw.activationNoticeShown === true) return;
3901
+ saveConfigPatch({ activationNoticeShown: true });
3902
+ write(ACTIVATION_NOTICE);
3903
+ } catch {
3904
+ }
3905
+ }
3906
+
3700
3907
  // src/gsc-sync.ts
3701
3908
  import crypto5 from "crypto";
3702
3909
  import { eq as eq3, and as and3, sql as sql3 } from "drizzle-orm";
@@ -7032,7 +7239,7 @@ function readStoredGroundingSources(rawResponse) {
7032
7239
  return result;
7033
7240
  }
7034
7241
  async function backfillInsightsCommand(project, opts) {
7035
- const { IntelligenceService: IntelligenceService2 } = await import("./intelligence-service-BD6BHTEJ.js");
7242
+ const { IntelligenceService: IntelligenceService2 } = await import("./intelligence-service-5RJHJLYA.js");
7036
7243
  const config = loadConfig();
7037
7244
  const db = createClient(config.database);
7038
7245
  migrate(db);
@@ -11930,7 +12137,12 @@ async function createServer(opts) {
11930
12137
  }
11931
12138
  const port = opts.config.port ?? 4100;
11932
12139
  const serverUrl = `http://localhost:${port}`;
11933
- const jobRunner = new JobRunner(opts.db, registry);
12140
+ const jobRunner = new JobRunner(opts.db, registry, {
12141
+ // The one-time first-sweep thank-you. Lives on the serve console because
12142
+ // runs execute here, including the foreground serve that init hands off
12143
+ // to. TTY-gated inside, so supervised deployments never see it.
12144
+ onFirstActivation: () => maybeShowActivationNotice()
12145
+ });
11934
12146
  jobRunner.recoverStaleRuns();
11935
12147
  const notifier = new Notifier(opts.db, serverUrl);
11936
12148
  const intelligenceService = new IntelligenceService(opts.db);
@@ -13059,6 +13271,7 @@ async function createServer(opts) {
13059
13271
  app.log.error({ runId, err }, "Job runner failed");
13060
13272
  });
13061
13273
  },
13274
+ getRunnableProviderNames: () => registry.getAll().map((provider) => provider.adapter.name),
13062
13275
  onProviderUpdate: (providerName, apiKey, model, baseUrl, incomingQuota) => {
13063
13276
  const name = providerName;
13064
13277
  if (!adapterMap[name]) return null;
@@ -13196,6 +13409,10 @@ async function createServer(opts) {
13196
13409
  saveConfigPatch(config);
13197
13410
  opts.config.telemetry = enabled;
13198
13411
  },
13412
+ recordOnboardingEvent: (event) => {
13413
+ const { event: eventName, eventId, ...properties } = event;
13414
+ trackEvent(eventName, properties, { source: "dashboard", eventId });
13415
+ },
13199
13416
  onCdpConfigure: async (host, port2) => {
13200
13417
  if (!opts.config.cdp) opts.config.cdp = {};
13201
13418
  opts.config.cdp.host = host;
@@ -13434,6 +13651,7 @@ function parseQueryResponse(raw, count) {
13434
13651
  }
13435
13652
 
13436
13653
  export {
13654
+ trackCliCommandFinished,
13437
13655
  setTelemetrySource,
13438
13656
  isTelemetryEnabled,
13439
13657
  getOrCreateAnonymousId,