@ainyc/canonry 4.107.0 → 4.109.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 (26) hide show
  1. package/assets/assets/{BacklinksPage-Cuvg9TTn.js → BacklinksPage-D48kAAiT.js} +1 -1
  2. package/assets/assets/{ChartPrimitives-CoNjLN3a.js → ChartPrimitives-CLSeu1W4.js} +1 -1
  3. package/assets/assets/{ProjectPage-DhEoUnmx.js → ProjectPage-D20d2kjG.js} +1 -1
  4. package/assets/assets/{RunRow-Du9r-Mik.js → RunRow-DQy-Gy9t.js} +1 -1
  5. package/assets/assets/{RunsPage-LIvuym9f.js → RunsPage-DRRG4uZS.js} +1 -1
  6. package/assets/assets/SettingsPage-Pf0B9gMT.js +1 -0
  7. package/assets/assets/TrafficPage-Cd2OL96L.js +1 -0
  8. package/assets/assets/{TrafficSourceDetailPage-BtOwcgWX.js → TrafficSourceDetailPage-CiyttCo2.js} +1 -1
  9. package/assets/assets/{arrow-left-DL0hZP8l.js → arrow-left-DaIR4jsx.js} +1 -1
  10. package/assets/assets/{extract-error-message-CgHHv6eH.js → extract-error-message-HqopXrW4.js} +1 -1
  11. package/assets/assets/index-CjrSrPKf.css +1 -0
  12. package/assets/assets/{index-54KuHvPl.js → index-CthlUeHu.js} +54 -54
  13. package/assets/assets/{trash-2-CpYdnCg2.js → trash-2-DUel-9g8.js} +1 -1
  14. package/assets/index.html +2 -2
  15. package/dist/{chunk-QLWRJEII.js → chunk-3HKBUYIY.js} +80 -18
  16. package/dist/{chunk-KRP555VO.js → chunk-QZQLZL3C.js} +1 -1
  17. package/dist/{chunk-MV3ZJOI7.js → chunk-WXAHSQOX.js} +47 -0
  18. package/dist/{chunk-NDXV6EYD.js → chunk-YEOLQT5S.js} +8 -5
  19. package/dist/cli.js +4 -4
  20. package/dist/index.js +4 -4
  21. package/dist/{intelligence-service-2DQLZUPW.js → intelligence-service-NXZXNM7T.js} +2 -2
  22. package/dist/mcp.js +2 -2
  23. package/package.json +8 -8
  24. package/assets/assets/SettingsPage-BrFy3IsH.js +0 -1
  25. package/assets/assets/TrafficPage-C16J18tV.js +0 -1
  26. package/assets/assets/index-DtsuPsZB.css +0 -1
@@ -1 +1 @@
1
- import{c}from"./index-54KuHvPl.js";const a=[["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}],["path",{d:"m9 12 2 2 4-4",key:"dzmm74"}]],h=c("circle-check",a);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"}]],n=c("circle-question-mark",e);const o=[["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=c("download",o);const t=[["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"}]],r=c("trash-2",t);export{h as C,s as D,r as T,n as a};
1
+ import{c}from"./index-CthlUeHu.js";const a=[["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}],["path",{d:"m9 12 2 2 4-4",key:"dzmm74"}]],h=c("circle-check",a);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"}]],n=c("circle-question-mark",e);const o=[["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=c("download",o);const t=[["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"}]],r=c("trash-2",t);export{h as C,s as D,r as T,n 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-54KuHvPl.js"></script>
15
+ <script type="module" crossorigin src="./assets/index-CthlUeHu.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-C9EZkgbP.js">
19
19
  <link rel="modulepreload" crossorigin href="./assets/vendor-markdown-6dwjPOKK.js">
20
- <link rel="stylesheet" crossorigin href="./assets/index-DtsuPsZB.css">
20
+ <link rel="stylesheet" crossorigin href="./assets/index-CjrSrPKf.css">
21
21
  </head>
22
22
  <body>
23
23
  <div id="root"></div>
@@ -94,6 +94,7 @@ import {
94
94
  coerceSkillManifest,
95
95
  competitorBatchRequestSchema,
96
96
  competitorDtoSchema,
97
+ computeDedupSimilarityStats,
97
98
  contentActionLabel,
98
99
  contentGapsResponseDtoSchema,
99
100
  contentSourcesResponseDtoSchema,
@@ -184,6 +185,7 @@ import {
184
185
  notFound,
185
186
  notImplemented,
186
187
  notificationDtoSchema,
188
+ orderLocationsDefaultFirst,
187
189
  parseInclusiveEndMs,
188
190
  parseReportPeriodDays,
189
191
  parseRunError,
@@ -268,10 +270,10 @@ import {
268
270
  wordpressSchemaDeployResultDtoSchema,
269
271
  wordpressSchemaStatusResultDtoSchema,
270
272
  wordpressStatusDtoSchema
271
- } from "./chunk-MV3ZJOI7.js";
273
+ } from "./chunk-WXAHSQOX.js";
272
274
 
273
275
  // src/intelligence-service.ts
274
- import { eq as eq38, desc as desc18, asc as asc6, and as and28, ne as ne5, or as or5, inArray as inArray14, gte as gte7, lte as lte4 } from "drizzle-orm";
276
+ import { eq as eq38, desc as desc18, asc as asc6, and as and28, ne as ne5, or as or6, inArray as inArray14, gte as gte7, lte as lte4 } from "drizzle-orm";
275
277
 
276
278
  // ../db/src/client.ts
277
279
  import { mkdirSync } from "fs";
@@ -1148,6 +1150,16 @@ var discoverySessions = sqliteTable("discovery_sessions", {
1148
1150
  // Buyer definition the session was seeded with; part of session identity
1149
1151
  // for in-flight consolidation (same ICP + different buyer never reuses).
1150
1152
  buyerDescription: text("buyer_description"),
1153
+ // Resolved service areas the session was seeded/probed with; part of session
1154
+ // identity for in-flight consolidation. Null on legacy sessions.
1155
+ locations: text("locations", { mode: "json" }).$type(),
1156
+ // Full seed provenance + dedup calibration diagnostics (nullable, legacy
1157
+ // sessions stay null). seed_raw_candidates is the seed dep's original list,
1158
+ // pre-brand-filter, so filter and dedup changes can replay real sessions.
1159
+ seedRawCandidates: text("seed_raw_candidates", { mode: "json" }).$type(),
1160
+ dedupClusterMinSims: text("dedup_cluster_min_sims", { mode: "json" }).$type(),
1161
+ dedupBandPairFraction: real("dedup_band_pair_fraction"),
1162
+ dedupPairsTotal: integer("dedup_pairs_total"),
1151
1163
  dedupThreshold: real("dedup_threshold"),
1152
1164
  probeCount: integer("probe_count"),
1153
1165
  citedCount: integer("cited_count"),
@@ -3417,6 +3429,29 @@ var MIGRATION_VERSIONS = [
3417
3429
  // reuse another buyer's session) and auditability for seed provenance.
3418
3430
  `ALTER TABLE discovery_sessions ADD COLUMN buyer_description TEXT`
3419
3431
  ]
3432
+ },
3433
+ {
3434
+ version: 91,
3435
+ name: "discovery-session-locations",
3436
+ statements: [
3437
+ // Resolved service areas the session was seeded/probed with. Part of the
3438
+ // in-flight consolidation identity (a different location subset never
3439
+ // reuses another geo's session) and seed/probe provenance. Nullable —
3440
+ // legacy sessions stay null.
3441
+ `ALTER TABLE discovery_sessions ADD COLUMN locations TEXT`
3442
+ ]
3443
+ },
3444
+ {
3445
+ version: 92,
3446
+ name: "discovery-session-dedup-diagnostics",
3447
+ statements: [
3448
+ // Seed provenance + dedup calibration diagnostics. Additive + nullable —
3449
+ // legacy sessions stay null; old writers omit them (downgrade-safe).
3450
+ `ALTER TABLE discovery_sessions ADD COLUMN seed_raw_candidates TEXT`,
3451
+ `ALTER TABLE discovery_sessions ADD COLUMN dedup_cluster_min_sims TEXT`,
3452
+ `ALTER TABLE discovery_sessions ADD COLUMN dedup_band_pair_fraction REAL`,
3453
+ `ALTER TABLE discovery_sessions ADD COLUMN dedup_pairs_total INTEGER`
3454
+ ]
3420
3455
  }
3421
3456
  ];
3422
3457
  function rebuildBacklinkTableWithSource(tx, table) {
@@ -3609,7 +3644,7 @@ function getAppliedVersion(db) {
3609
3644
  function recordMigration(db, version, name) {
3610
3645
  db.run(sql`INSERT OR IGNORE INTO _migrations (version, name) VALUES (${version}, ${name})`);
3611
3646
  }
3612
- function migrate(db) {
3647
+ function migrate(db, versions = MIGRATION_VERSIONS) {
3613
3648
  db.transaction((tx) => {
3614
3649
  normalizeLegacyQuerySchema(tx);
3615
3650
  });
@@ -3618,7 +3653,7 @@ function migrate(db) {
3618
3653
  db.run(sql.raw(statement));
3619
3654
  }
3620
3655
  const appliedVersion = getAppliedVersion(db);
3621
- for (const mv of MIGRATION_VERSIONS) {
3656
+ for (const mv of versions) {
3622
3657
  if (mv.version <= appliedVersion) continue;
3623
3658
  db.transaction((tx) => {
3624
3659
  for (const statement of mv.statements) {
@@ -33757,7 +33792,7 @@ async function doctorRoutes(app, opts) {
33757
33792
 
33758
33793
  // ../api-routes/src/discovery/routes.ts
33759
33794
  import crypto27 from "crypto";
33760
- import { and as and26, desc as desc16, eq as eq35, gte as gte6, inArray as inArray12, isNull } from "drizzle-orm";
33795
+ import { and as and26, desc as desc16, eq as eq35, gte as gte6, inArray as inArray12, isNull, or as or5 } from "drizzle-orm";
33761
33796
  var MAX_INFLIGHT_DISCOVERY_AGE_MS = 2 * 60 * 60 * 1e3;
33762
33797
  async function discoveryRoutes(app, opts) {
33763
33798
  app.post("/projects/:name/discover/run", async (request, reply) => {
@@ -33777,9 +33812,9 @@ async function discoveryRoutes(app, opts) {
33777
33812
  "icpDescription is required. Pass it in the request body or store it on the project (spec.icpDescription)."
33778
33813
  );
33779
33814
  }
33780
- const locations = resolveLocations(
33781
- project.locations,
33782
- parsed.data.locations
33815
+ const locations = orderLocationsDefaultFirst(
33816
+ resolveLocations(project.locations, parsed.data.locations),
33817
+ project.defaultLocation
33783
33818
  );
33784
33819
  if (!opts.onDiscoveryRunRequested) {
33785
33820
  throw validationError("Discovery is not available on this deployment.", {
@@ -33796,6 +33831,16 @@ async function discoveryRoutes(app, opts) {
33796
33831
  // semantics, so a request with a different (or no) buyer must start
33797
33832
  // its own session, never adopt another buyer's probes.
33798
33833
  parsed.data.buyerDescription == null ? isNull(discoverySessions.buyerDescription) : eq35(discoverySessions.buyerDescription, parsed.data.buyerDescription),
33834
+ // Locations are identity too: a different service-area subset seeds
33835
+ // and probes a different geo, so it must never reuse another geo's
33836
+ // session. resolveLocations is deterministic (project-config order),
33837
+ // so equal subsets always serialize identically. Legacy in-flight
33838
+ // rows (pre-91) carry NULL: on a NO-location project that geo is
33839
+ // unambiguous (always []), so NULL matches; on a project WITH
33840
+ // locations a NULL row's subset is unknowable, so it conservatively
33841
+ // never reuses — a one-time, bounded (2h window) non-reuse after
33842
+ // upgrade, never a wrong reuse.
33843
+ locations.length === 0 ? or5(isNull(discoverySessions.locations), eq35(discoverySessions.locations, locations)) : eq35(discoverySessions.locations, locations),
33799
33844
  inArray12(discoverySessions.status, [
33800
33845
  DiscoverySessionStatuses.queued,
33801
33846
  DiscoverySessionStatuses.seeding,
@@ -33815,6 +33860,7 @@ async function discoveryRoutes(app, opts) {
33815
33860
  status: DiscoverySessionStatuses.queued,
33816
33861
  icpDescription,
33817
33862
  buyerDescription: parsed.data.buyerDescription ?? null,
33863
+ locations,
33818
33864
  dedupThreshold: parsed.data.dedupThreshold,
33819
33865
  competitorMap: [],
33820
33866
  createdAt: now
@@ -34106,6 +34152,11 @@ function serializeSession(row) {
34106
34152
  seedFromGroundingCount: row.seedFromGroundingCount ?? null,
34107
34153
  seedBrandFilteredCount: row.seedBrandFilteredCount ?? null,
34108
34154
  buyerDescription: row.buyerDescription ?? null,
34155
+ locations: row.locations ?? null,
34156
+ seedRawCandidates: row.seedRawCandidates ?? null,
34157
+ dedupClusterMinSims: row.dedupClusterMinSims ?? null,
34158
+ dedupBandPairFraction: row.dedupBandPairFraction ?? null,
34159
+ dedupPairsTotal: row.dedupPairsTotal ?? null,
34109
34160
  dedupThreshold: row.dedupThreshold ?? null,
34110
34161
  probeCount: row.probeCount ?? null,
34111
34162
  citedCount: row.citedCount ?? null,
@@ -34188,12 +34239,14 @@ async function classifyCompetitorDomains(deps, project, icpDescription, domains)
34188
34239
  return {};
34189
34240
  }
34190
34241
  }
34191
- async function pickCanonicals(candidates, deps, dedupThreshold) {
34192
- if (candidates.length === 0) return [];
34193
- if (candidates.length === 1) return candidates;
34242
+ async function pickCanonicalsWithStats(candidates, deps, dedupThreshold) {
34243
+ const emptyStats = { perClusterMinSimilarity: [], bandPairFraction: null, pairsTotal: 0 };
34244
+ if (candidates.length === 0) return { canonicals: [], stats: emptyStats };
34245
+ if (candidates.length === 1) return { canonicals: candidates, stats: emptyStats };
34194
34246
  const vectors = await deps.embed(candidates);
34195
- const clusters = clusterByCosine(candidates, vectors, dedupThreshold);
34196
- return clusters.map(pickClusterRepresentative);
34247
+ const indexClusters = clusterByCosine(candidates.map((_, i) => i), vectors, dedupThreshold);
34248
+ const canonicals = indexClusters.map((cluster) => pickClusterRepresentative(cluster.map((i) => candidates[i])));
34249
+ return { canonicals, stats: computeDedupSimilarityStats(vectors, indexClusters) };
34197
34250
  }
34198
34251
  async function executeDiscovery(opts) {
34199
34252
  const dedupThreshold = opts.dedupThreshold ?? DISCOVERY_DEFAULT_DEDUP_THRESHOLD;
@@ -34222,7 +34275,7 @@ async function executeDiscovery(opts) {
34222
34275
  });
34223
34276
  const rawCandidates = dedupeStrings(unbrandedCandidates);
34224
34277
  const seedCountRaw = rawCandidates.length;
34225
- const canonicals = await pickCanonicals(
34278
+ const { canonicals, stats: dedupStats } = await pickCanonicalsWithStats(
34226
34279
  rawCandidates,
34227
34280
  { embed: opts.deps.embed },
34228
34281
  dedupThreshold
@@ -34244,12 +34297,21 @@ async function executeDiscovery(opts) {
34244
34297
  seedFromAnswerCount: seedResult.fromAnswerCount ?? null,
34245
34298
  seedFromGroundingCount: seedResult.fromGroundingCount ?? null,
34246
34299
  seedBrandFilteredCount: droppedBranded.length,
34300
+ // Full seed provenance + dedup calibration diagnostics: the raw
34301
+ // candidate list (as the seed dep returned it, pre-filter) makes every
34302
+ // live session a replayable fixture; the similarity stats are the data
34303
+ // the 0.95-threshold decision was missing. No gate reads any of these.
34304
+ seedRawCandidates: seedResult.candidates,
34305
+ dedupClusterMinSims: dedupStats.perClusterMinSimilarity,
34306
+ dedupBandPairFraction: dedupStats.bandPairFraction,
34307
+ dedupPairsTotal: dedupStats.pairsTotal,
34247
34308
  warning
34248
34309
  }).where(eq36(discoverySessions.id, opts.sessionId)).run();
34310
+ const probeLocation = opts.locations?.[0];
34249
34311
  const probeResults = await mapWithConcurrency(
34250
34312
  probedCanonicals,
34251
34313
  probeConcurrency,
34252
- (query) => opts.deps.probe({ project: opts.project, query })
34314
+ (query) => opts.deps.probe({ project: opts.project, query, location: probeLocation })
34253
34315
  );
34254
34316
  const probeRows = [];
34255
34317
  const buckets = { cited: 0, aspirational: 0, "wasted-surface": 0 };
@@ -35068,7 +35130,7 @@ var IntelligenceService = class {
35068
35130
  const recentRuns = this.db.select().from(runs).where(
35069
35131
  and28(
35070
35132
  eq38(runs.projectId, projectId),
35071
- or5(eq38(runs.status, "completed"), eq38(runs.status, "partial")),
35133
+ or6(eq38(runs.status, "completed"), eq38(runs.status, "partial")),
35072
35134
  // Defensive: RunCoordinator already skips probes before this is
35073
35135
  // called, but if a future call site invokes analyzeAndPersist
35074
35136
  // directly for a probe, probes still must not pollute the
@@ -35371,7 +35433,7 @@ var IntelligenceService = class {
35371
35433
  const allRuns = this.db.select().from(runs).where(
35372
35434
  and28(
35373
35435
  eq38(runs.projectId, project.id),
35374
- or5(eq38(runs.status, "completed"), eq38(runs.status, "partial")),
35436
+ or6(eq38(runs.status, "completed"), eq38(runs.status, "partial")),
35375
35437
  // Backfill must not replay probe runs as if they were real sweeps.
35376
35438
  ne5(runs.trigger, RunTriggers.probe)
35377
35439
  )
@@ -35549,7 +35611,7 @@ var IntelligenceService = class {
35549
35611
  and28(
35550
35612
  eq38(runs.projectId, projectId),
35551
35613
  eq38(runs.kind, RunKinds["answer-visibility"]),
35552
- or5(eq38(runs.status, "completed"), eq38(runs.status, "partial")),
35614
+ or6(eq38(runs.status, "completed"), eq38(runs.status, "partial")),
35553
35615
  // Defensive — see top of file.
35554
35616
  ne5(runs.trigger, RunTriggers.probe)
35555
35617
  )
@@ -25,7 +25,7 @@ import {
25
25
  trafficConnectVercelRequestSchema,
26
26
  trafficConnectWordpressRequestSchema,
27
27
  trafficEventKindSchema
28
- } from "./chunk-MV3ZJOI7.js";
28
+ } from "./chunk-WXAHSQOX.js";
29
29
 
30
30
  // src/config.ts
31
31
  import fs from "fs";
@@ -169,6 +169,12 @@ function hasLocationLabel(locations, label) {
169
169
  if (!label) return true;
170
170
  return locations.some((location) => location.label === label);
171
171
  }
172
+ function orderLocationsDefaultFirst(locations, defaultLabel) {
173
+ if (!defaultLabel) return [...locations];
174
+ const index = locations.findIndex((l) => l.label === defaultLabel);
175
+ if (index <= 0) return [...locations];
176
+ return [locations[index], ...locations.slice(0, index), ...locations.slice(index + 1)];
177
+ }
172
178
  function resolveLocations(projectLocations, requestedLabels) {
173
179
  const normalizedRequest = (requestedLabels ?? []).map((label) => label.trim()).filter((label) => label.length > 0);
174
180
  if (normalizedRequest.length === 0) return [...projectLocations];
@@ -1987,6 +1993,34 @@ function pickClusterRepresentative(cluster) {
1987
1993
  }
1988
1994
  return best;
1989
1995
  }
1996
+ var DEDUP_SIMILARITY_BAND_LOW = 0.9;
1997
+ var DEDUP_SIMILARITY_BAND_HIGH = 0.97;
1998
+ function computeDedupSimilarityStats(vectors, indexClusters) {
1999
+ const n = vectors.length;
2000
+ if (n < 2) return { perClusterMinSimilarity: [], bandPairFraction: null, pairsTotal: 0 };
2001
+ const round = (v) => Math.round(v * 1e4) / 1e4;
2002
+ const perClusterMinSimilarity = [];
2003
+ for (const cluster of indexClusters) {
2004
+ if (cluster.length < 2) continue;
2005
+ let min = Infinity;
2006
+ for (let a = 0; a < cluster.length; a++) {
2007
+ for (let b = a + 1; b < cluster.length; b++) {
2008
+ min = Math.min(min, cosineSimilarity(vectors[cluster[a]], vectors[cluster[b]]));
2009
+ }
2010
+ }
2011
+ perClusterMinSimilarity.push(round(min));
2012
+ }
2013
+ let inBand = 0;
2014
+ let pairsTotal = 0;
2015
+ for (let i = 0; i < n; i++) {
2016
+ for (let j = i + 1; j < n; j++) {
2017
+ pairsTotal++;
2018
+ const sim = cosineSimilarity(vectors[i], vectors[j]);
2019
+ if (sim >= DEDUP_SIMILARITY_BAND_LOW && sim < DEDUP_SIMILARITY_BAND_HIGH) inBand++;
2020
+ }
2021
+ }
2022
+ return { perClusterMinSimilarity, bandPairFraction: round(inBand / pairsTotal), pairsTotal };
2023
+ }
1990
2024
 
1991
2025
  // ../contracts/src/url-normalize.ts
1992
2026
  var STRIP_KEYS = /* @__PURE__ */ new Set([
@@ -2196,6 +2230,17 @@ var discoverySessionDtoSchema = z17.object({
2196
2230
  /** Buyer definition the session was seeded with (part of session identity
2197
2231
  * for in-flight consolidation). Null on legacy / no-buyer sessions. */
2198
2232
  buyerDescription: z17.string().nullable().optional(),
2233
+ /** Resolved service areas the session was seeded/probed with (part of
2234
+ * session identity for in-flight consolidation). Null on legacy sessions. */
2235
+ locations: z17.array(locationContextSchema).nullable().optional(),
2236
+ /** Seed provenance: the seed dep's original candidate list (pre-filter),
2237
+ * so filter/dedup changes replay against real sessions. Null on legacy. */
2238
+ seedRawCandidates: z17.array(z17.string()).nullable().optional(),
2239
+ /** Dedup calibration: min pairwise cosine per multi-member cluster. */
2240
+ dedupClusterMinSims: z17.array(z17.number()).nullable().optional(),
2241
+ /** Dedup calibration: fraction of all pairs in the ambiguous 0.90-0.97 band. */
2242
+ dedupBandPairFraction: z17.number().nullable().optional(),
2243
+ dedupPairsTotal: z17.number().int().nullable().optional(),
2199
2244
  dedupThreshold: z17.number().nullable().optional(),
2200
2245
  probeCount: z17.number().int().nullable().optional(),
2201
2246
  citedCount: z17.number().int().nullable().default(null),
@@ -5591,6 +5636,7 @@ export {
5591
5636
  runtimeStateMissing,
5592
5637
  findDuplicateLocationLabels,
5593
5638
  hasLocationLabel,
5639
+ orderLocationsDefaultFirst,
5594
5640
  resolveLocations,
5595
5641
  projectUpsertRequestSchema,
5596
5642
  projectDtoSchema,
@@ -5701,6 +5747,7 @@ export {
5701
5747
  categoryLabel,
5702
5748
  clusterByCosine,
5703
5749
  pickClusterRepresentative,
5750
+ computeDedupSimilarityStats,
5704
5751
  absolutizeProjectUrl,
5705
5752
  hostOf,
5706
5753
  normalizeUrlPath,
@@ -10,7 +10,7 @@ import {
10
10
  loadConfig,
11
11
  loadConfigRaw,
12
12
  saveConfigPatch
13
- } from "./chunk-KRP555VO.js";
13
+ } from "./chunk-QZQLZL3C.js";
14
14
  import {
15
15
  CC_CACHE_DIR,
16
16
  DUCKDB_SPEC,
@@ -115,7 +115,7 @@ import {
115
115
  siteAuditPages,
116
116
  siteAuditSnapshots,
117
117
  usageCounters
118
- } from "./chunk-QLWRJEII.js";
118
+ } from "./chunk-3HKBUYIY.js";
119
119
  import {
120
120
  AGENT_MEMORY_VALUE_MAX_BYTES,
121
121
  AGENT_PROVIDER_IDS,
@@ -178,7 +178,7 @@ import {
178
178
  validationError,
179
179
  winnabilityClassLabel,
180
180
  withRetry
181
- } from "./chunk-MV3ZJOI7.js";
181
+ } from "./chunk-WXAHSQOX.js";
182
182
 
183
183
  // src/telemetry.ts
184
184
  import crypto from "crypto";
@@ -5649,7 +5649,10 @@ function buildDefaultDeps(registry) {
5649
5649
  {
5650
5650
  query: input.query,
5651
5651
  canonicalDomains: input.project.canonicalDomains,
5652
- competitorDomains: input.project.competitorDomains
5652
+ competitorDomains: input.project.competitorDomains,
5653
+ // Same geo mechanism as sweeps: the provider renders the location
5654
+ // into the prompt so the probe measures from the buyer's area.
5655
+ ...input.location ? { location: input.location } : {}
5653
5656
  },
5654
5657
  cfg
5655
5658
  );
@@ -6482,7 +6485,7 @@ function readStoredGroundingSources(rawResponse) {
6482
6485
  return result;
6483
6486
  }
6484
6487
  async function backfillInsightsCommand(project, opts) {
6485
- const { IntelligenceService: IntelligenceService2 } = await import("./intelligence-service-2DQLZUPW.js");
6488
+ const { IntelligenceService: IntelligenceService2 } = await import("./intelligence-service-NXZXNM7T.js");
6486
6489
  const config = loadConfig();
6487
6490
  const db = createClient(config.database);
6488
6491
  migrate(db);
package/dist/cli.js CHANGED
@@ -29,7 +29,7 @@ import {
29
29
  setTelemetrySource,
30
30
  showFirstRunNotice,
31
31
  trackEvent
32
- } from "./chunk-NDXV6EYD.js";
32
+ } from "./chunk-YEOLQT5S.js";
33
33
  import {
34
34
  CliError,
35
35
  EXIT_SYSTEM_ERROR,
@@ -46,7 +46,7 @@ import {
46
46
  saveConfig,
47
47
  saveConfigPatch,
48
48
  usageError
49
- } from "./chunk-KRP555VO.js";
49
+ } from "./chunk-QZQLZL3C.js";
50
50
  import {
51
51
  apiKeys,
52
52
  createClient,
@@ -54,7 +54,7 @@ import {
54
54
  projects,
55
55
  queries,
56
56
  renderReportHtml
57
- } from "./chunk-QLWRJEII.js";
57
+ } from "./chunk-3HKBUYIY.js";
58
58
  import {
59
59
  BacklinkSources,
60
60
  CcReleaseSyncStatuses,
@@ -80,7 +80,7 @@ import {
80
80
  providerQuotaPolicySchema,
81
81
  resolveProviderInput,
82
82
  winnabilityClassSchema
83
- } from "./chunk-MV3ZJOI7.js";
83
+ } from "./chunk-WXAHSQOX.js";
84
84
 
85
85
  // src/cli.ts
86
86
  import { pathToFileURL } from "url";
package/dist/index.js CHANGED
@@ -1,11 +1,11 @@
1
1
  import {
2
2
  createServer
3
- } from "./chunk-NDXV6EYD.js";
3
+ } from "./chunk-YEOLQT5S.js";
4
4
  import {
5
5
  loadConfig
6
- } from "./chunk-KRP555VO.js";
7
- import "./chunk-QLWRJEII.js";
8
- import "./chunk-MV3ZJOI7.js";
6
+ } from "./chunk-QZQLZL3C.js";
7
+ import "./chunk-3HKBUYIY.js";
8
+ import "./chunk-WXAHSQOX.js";
9
9
  export {
10
10
  createServer,
11
11
  loadConfig
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  IntelligenceService
3
- } from "./chunk-QLWRJEII.js";
4
- import "./chunk-MV3ZJOI7.js";
3
+ } from "./chunk-3HKBUYIY.js";
4
+ import "./chunk-WXAHSQOX.js";
5
5
  export {
6
6
  IntelligenceService
7
7
  };
package/dist/mcp.js CHANGED
@@ -3,10 +3,10 @@ import {
3
3
  PACKAGE_VERSION,
4
4
  canonryMcpTools,
5
5
  createApiClient
6
- } from "./chunk-KRP555VO.js";
6
+ } from "./chunk-QZQLZL3C.js";
7
7
  import {
8
8
  isReadOnlyKey
9
- } from "./chunk-MV3ZJOI7.js";
9
+ } from "./chunk-WXAHSQOX.js";
10
10
 
11
11
  // src/mcp/cli.ts
12
12
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ainyc/canonry",
3
- "version": "4.107.0",
3
+ "version": "4.109.0",
4
4
  "type": "module",
5
5
  "description": "Agent-first open-source AEO operating platform - track how answer engines cite your domain",
6
6
  "license": "FSL-1.1-ALv2",
@@ -65,26 +65,26 @@
65
65
  "@types/node-cron": "^3.0.11",
66
66
  "tsup": "^8.5.1",
67
67
  "tsx": "^4.19.0",
68
- "@ainyc/canonry-api-client": "0.0.0",
69
- "@ainyc/canonry-config": "0.0.0",
70
68
  "@ainyc/canonry-api-routes": "0.0.0",
71
69
  "@ainyc/canonry-contracts": "0.0.0",
70
+ "@ainyc/canonry-config": "0.0.0",
71
+ "@ainyc/canonry-api-client": "0.0.0",
72
72
  "@ainyc/canonry-db": "0.0.0",
73
73
  "@ainyc/canonry-integration-bing": "0.0.0",
74
74
  "@ainyc/canonry-integration-openai-ads": "0.0.0",
75
- "@ainyc/canonry-integration-cloud-run": "0.0.0",
76
75
  "@ainyc/canonry-integration-commoncrawl": "0.0.0",
77
- "@ainyc/canonry-integration-google-places": "0.0.0",
76
+ "@ainyc/canonry-integration-cloud-run": "0.0.0",
78
77
  "@ainyc/canonry-integration-google": "0.0.0",
79
- "@ainyc/canonry-integration-wordpress": "0.0.0",
80
78
  "@ainyc/canonry-integration-google-business-profile": "0.0.0",
79
+ "@ainyc/canonry-integration-google-places": "0.0.0",
80
+ "@ainyc/canonry-integration-wordpress": "0.0.0",
81
81
  "@ainyc/canonry-integration-traffic": "0.0.0",
82
82
  "@ainyc/canonry-intelligence": "0.0.0",
83
- "@ainyc/canonry-provider-claude": "0.0.0",
84
83
  "@ainyc/canonry-provider-cdp": "0.0.0",
85
- "@ainyc/canonry-provider-gemini": "0.0.0",
84
+ "@ainyc/canonry-provider-claude": "0.0.0",
86
85
  "@ainyc/canonry-provider-local": "0.0.0",
87
86
  "@ainyc/canonry-provider-openai": "0.0.0",
87
+ "@ainyc/canonry-provider-gemini": "0.0.0",
88
88
  "@ainyc/canonry-provider-perplexity": "0.0.0"
89
89
  },
90
90
  "scripts": {
@@ -1 +0,0 @@
1
- import{r as n,j as e}from"./vendor-tanstack-Be8JdV5M.js";import{B as f,i as k,bM as $,M as A,bN as H,bO as E,g as b,T as y,bP as q,ba as Q,bJ as V,bQ as W,bR as _,bS as F,bT as T}from"./index-54KuHvPl.js";import"./vendor-radix-CQ77EO2y.js";import"./vendor-recharts-C9EZkgbP.js";import"./vendor-markdown-6dwjPOKK.js";function J({providerName:s,keyUrl:m,modelHint:l,onSaved:j}){const u=s.toLowerCase()==="local",[c,o]=n.useState(""),[x,d]=n.useState(""),[g,h]=n.useState(""),[p,a]=n.useState(""),[i,v]=n.useState(""),[N,S]=n.useState(""),[z,P]=n.useState(!1),[C,t]=n.useState(null),[K,I]=n.useState(!1),M=u?x.trim().length>0:c.trim().length>0;async function L(){if(M){P(!0),t(null),I(!1);try{const r=U=>{const B=parseInt(U.trim(),10);return Number.isFinite(B)&&B>0?B:void 0},w={},O=r(p);O!==void 0&&(w.maxConcurrency=O);const D=r(i);D!==void 0&&(w.maxRequestsPerMinute=D);const G=r(N);G!==void 0&&(w.maxRequestsPerDay=G),await $(s.toLowerCase(),{...c.trim()?{apiKey:c.trim()}:{},...x.trim()?{baseUrl:x.trim()}:{},...g.trim()?{model:g.trim()}:{},...Object.keys(w).length>0?{quota:w}:{}}),o(""),d(""),h(""),a(""),v(""),S(""),I(!0),A({title:"Provider updated",detail:`${s} configuration saved.`,tone:"positive",dedupeKey:`settings:provider:${s}`,dedupeMode:"replace"}),j()}catch(r){t(r instanceof Error?r.message:"Failed to update provider")}finally{P(!1)}}}const R=l??"Use default model";return e.jsxs("div",{className:"mt-3 rounded-lg border border-zinc-800 bg-zinc-900/40 p-3 space-y-2",children:[u&&e.jsxs("div",{children:[e.jsx("label",{className:"text-xs text-zinc-500",htmlFor:`base-url-${s}`,children:"Base URL"}),e.jsx("input",{id:`base-url-${s}`,type:"text",className:"mt-0.5 w-full rounded border border-zinc-700 bg-transparent px-2 py-1.5 text-sm text-zinc-200 placeholder-zinc-600 focus:border-zinc-500 focus:outline-none",placeholder:"http://localhost:11434/v1",value:x,onChange:r=>d(r.target.value)}),e.jsx("p",{className:"mt-0.5 text-[10px] text-zinc-600",children:"Any OpenAI-compatible endpoint — Ollama, LM Studio, llama.cpp, vLLM"})]}),e.jsxs("div",{children:[e.jsxs("div",{className:"flex items-center justify-between",children:[e.jsxs("label",{className:"text-xs text-zinc-500",htmlFor:`api-key-${s}`,children:["API Key",u?" (optional)":""]}),m&&e.jsxs("a",{href:m,target:"_blank",rel:"noopener noreferrer",className:"text-[10px] text-zinc-500 hover:text-zinc-300 underline underline-offset-2",children:["Get API key ","↗"]})]}),e.jsx("input",{id:`api-key-${s}`,type:"password",className:"mt-0.5 w-full rounded border border-zinc-700 bg-transparent px-2 py-1.5 text-sm text-zinc-200 placeholder-zinc-600 focus:border-zinc-500 focus:outline-none",placeholder:u?"Optional — most local servers don't need one":`Enter ${s} API key`,value:c,onChange:r=>o(r.target.value)})]}),e.jsxs("div",{children:[e.jsx("label",{className:"text-xs text-zinc-500",htmlFor:`model-${s}`,children:"Model (optional)"}),e.jsx("input",{id:`model-${s}`,type:"text",className:"mt-0.5 w-full rounded border border-zinc-700 bg-transparent px-2 py-1.5 text-sm text-zinc-200 placeholder-zinc-600 focus:border-zinc-500 focus:outline-none",placeholder:R,value:g,onChange:r=>h(r.target.value)})]}),e.jsxs("div",{children:[e.jsx("label",{className:"text-xs text-zinc-500",children:"Quota (optional)"}),e.jsxs("div",{className:"mt-0.5 grid grid-cols-3 gap-1.5",children:[e.jsxs("div",{children:[e.jsx("input",{type:"number",min:"1",className:"w-full rounded border border-zinc-700 bg-transparent px-2 py-1.5 text-sm text-zinc-200 placeholder-zinc-600 focus:border-zinc-500 focus:outline-none",placeholder:"Concurrent",value:p,onChange:r=>a(r.target.value)}),e.jsx("p",{className:"mt-0.5 text-[10px] text-zinc-600",children:"Max concurrent"})]}),e.jsxs("div",{children:[e.jsx("input",{type:"number",min:"1",className:"w-full rounded border border-zinc-700 bg-transparent px-2 py-1.5 text-sm text-zinc-200 placeholder-zinc-600 focus:border-zinc-500 focus:outline-none",placeholder:"/min",value:i,onChange:r=>v(r.target.value)}),e.jsx("p",{className:"mt-0.5 text-[10px] text-zinc-600",children:"Per minute"})]}),e.jsxs("div",{children:[e.jsx("input",{type:"number",min:"1",className:"w-full rounded border border-zinc-700 bg-transparent px-2 py-1.5 text-sm text-zinc-200 placeholder-zinc-600 focus:border-zinc-500 focus:outline-none",placeholder:"/day",value:N,onChange:r=>S(r.target.value)}),e.jsx("p",{className:"mt-0.5 text-[10px] text-zinc-600",children:"Per day"})]})]})]}),C&&e.jsx("p",{className:"text-xs text-rose-400",children:C}),K&&e.jsx("p",{className:"text-xs text-emerald-400",children:"Provider updated."}),e.jsx(f,{type:"button",size:"sm",disabled:!M||z,onClick:k(L),children:z?"Saving...":"Save"})]})}function X({onSaved:s}){const[m,l]=n.useState(""),[j,u]=n.useState(""),[c,o]=n.useState(!1),[x,d]=n.useState(null),[g,h]=n.useState(!1),p=m.trim().length>0&&j.trim().length>0;async function a(){if(p){o(!0),d(null),h(!1);try{await H({clientId:m.trim(),clientSecret:j.trim()}),l(""),u(""),h(!0),A({title:"Google OAuth app updated",detail:"Dashboard Google credentials were saved.",tone:"positive",dedupeKey:"settings:google-oauth",dedupeMode:"replace"}),s()}catch(i){d(i instanceof Error?i.message:"Failed to update Google OAuth credentials")}finally{o(!1)}}}return e.jsxs("div",{className:"mt-3 rounded-lg border border-zinc-800 bg-zinc-900/40 p-3 space-y-2",children:[e.jsxs("div",{children:[e.jsxs("div",{className:"flex items-center justify-between",children:[e.jsx("label",{className:"text-xs text-zinc-500",htmlFor:"google-client-id",children:"Client ID"}),e.jsxs("a",{href:"https://console.cloud.google.com/apis/credentials",target:"_blank",rel:"noopener noreferrer",className:"text-[10px] text-zinc-500 hover:text-zinc-300 underline underline-offset-2",children:["Google Cloud ","↗"]})]}),e.jsx("input",{id:"google-client-id",type:"text",className:"mt-0.5 w-full rounded border border-zinc-700 bg-transparent px-2 py-1.5 text-sm text-zinc-200 placeholder-zinc-600 focus:border-zinc-500 focus:outline-none",placeholder:"Google OAuth client ID",value:m,onChange:i=>l(i.target.value)})]}),e.jsxs("div",{children:[e.jsx("label",{className:"text-xs text-zinc-500",htmlFor:"google-client-secret",children:"Client secret"}),e.jsx("input",{id:"google-client-secret",type:"password",className:"mt-0.5 w-full rounded border border-zinc-700 bg-transparent px-2 py-1.5 text-sm text-zinc-200 placeholder-zinc-600 focus:border-zinc-500 focus:outline-none",placeholder:"Google OAuth client secret",value:j,onChange:i=>u(i.target.value)})]}),e.jsxs("p",{className:"text-[11px] text-zinc-500",children:["These credentials are stored in ",e.jsx("code",{children:"~/.canonry/config.yaml"}),". Project-level Search Console connections are created separately per canonical domain."]}),x&&e.jsx("p",{className:"text-xs text-rose-400",children:x}),g&&e.jsx("p",{className:"text-xs text-emerald-400",children:"Google OAuth credentials updated."}),e.jsx(f,{type:"button",size:"sm",disabled:!p||c,onClick:k(a),children:c?"Saving...":"Save Google OAuth app"})]})}function Y(){const[s,m]=n.useState(null),[l,j]=n.useState(null),[u,c]=n.useState(!1),[o,x]=n.useState("localhost"),[d,g]=n.useState("9222"),[h,p]=n.useState(!1);return n.useEffect(()=>{E().then(m).catch(a=>{const i=a instanceof Error?a.message:String(a);i.includes("501")||j(i)})},[]),e.jsxs(b,{className:"surface-card",children:[e.jsxs("div",{className:"section-head",children:[e.jsxs("div",{children:[e.jsx("p",{className:"eyebrow eyebrow-soft",children:"Browser provider"}),e.jsx("h2",{children:"ChatGPT (CDP)"})]}),e.jsx(y,{tone:s?.connected?"positive":"caution",children:s?.connected?"Connected":"Not connected"})]}),e.jsxs("dl",{className:"definition-list mt-3",children:[s?.endpoint&&e.jsxs("div",{children:[e.jsx("dt",{children:"Endpoint"}),e.jsx("dd",{className:"font-mono text-xs",children:s.endpoint})]}),s?.browserVersion&&e.jsxs("div",{children:[e.jsx("dt",{children:"Browser"}),e.jsx("dd",{className:"text-xs",children:s.browserVersion})]}),s?.targets&&s.targets.length>0&&e.jsxs("div",{children:[e.jsx("dt",{children:"Tabs"}),e.jsx("dd",{children:s.targets.map(a=>e.jsxs("span",{className:"mr-2",children:[a.name,": ",a.alive?"● alive":"○ idle"]},a.name))})]})]}),e.jsx("p",{className:"mt-2 text-sm text-zinc-500",children:s?.connected?"Connected to Chrome via CDP. Launch Chrome with --remote-debugging-port to use this provider.":l||"Not configured. Set an endpoint below or run: canonry cdp connect --host localhost --port 9222"}),e.jsx("div",{className:"mt-2",children:e.jsx(f,{type:"button",variant:"outline",size:"sm",onClick:()=>c(!u),children:u?"Cancel":s?.connected?"Update endpoint":"Configure"})}),u&&e.jsxs("form",{className:"mt-3 flex flex-col gap-2",onSubmit:k(async a=>{a.preventDefault(),p(!0);try{await q(o,parseInt(d,10)||9222);const i=await E().catch(()=>null);i&&m(i),c(!1),A({title:"CDP endpoint saved",detail:`${o}:${parseInt(d,10)||9222} is now configured.`,tone:"positive",dedupeKey:"settings:cdp",dedupeMode:"replace"})}catch(i){j(i instanceof Error?i.message:String(i))}finally{p(!1)}}),children:[e.jsxs("div",{className:"flex gap-2",children:[e.jsx("input",{className:"flex-1 rounded border border-zinc-700 bg-zinc-900 px-2 py-1 text-sm text-zinc-100 placeholder-zinc-500 focus:outline-none focus:ring-1 focus:ring-zinc-500",placeholder:"localhost",value:o,onChange:a=>x(a.target.value),"aria-label":"CDP host"}),e.jsx("input",{className:"w-24 rounded border border-zinc-700 bg-zinc-900 px-2 py-1 text-sm text-zinc-100 placeholder-zinc-500 focus:outline-none focus:ring-1 focus:ring-zinc-500",placeholder:"9222",value:d,onChange:a=>g(a.target.value),"aria-label":"CDP port"})]}),e.jsx("div",{children:e.jsx(f,{type:"submit",size:"sm",disabled:h,children:h?"Saving…":"Save endpoint"})})]})]})}const Z={apiStatus:{state:"checking",detail:"Checking service health"},workerStatus:{state:"checking",detail:"Checking service health"}};function ie(){const s=Q(),{dashboard:m}=V(),l=m?.settings??s?.dashboard?.settings,c=W(!s,s?.health).data??s?.health??Z,[o,x]=n.useState(null),[d,g]=n.useState(!1),[h,p]=n.useState(!1),[a,i]=n.useState(""),[v,N]=n.useState(!1),[S,z]=n.useState(null),[P,C]=n.useState(!1);return l?e.jsxs("div",{className:"page-container",children:[e.jsx("div",{className:"page-header",children:e.jsxs("div",{className:"page-header-left",children:[e.jsx("h1",{className:"page-title",children:"Settings"}),e.jsx("p",{className:"page-subtitle",children:"Provider state, Google OAuth, Bing WMT setup, and service health."})]})}),e.jsxs("section",{className:"settings-grid",children:[l.providerStatuses.map(t=>e.jsxs(b,{className:"surface-card",children:[e.jsxs("div",{className:"section-head",children:[e.jsxs("div",{children:[e.jsx("p",{className:"eyebrow eyebrow-soft",children:"Provider"}),e.jsx("h2",{children:t.displayName??t.name})]}),e.jsx(y,{tone:t.state==="ready"?"positive":"caution",children:t.state==="ready"?"Ready":"Needs config"})]}),e.jsxs("dl",{className:"definition-list mt-3",children:[e.jsxs("div",{children:[e.jsx("dt",{children:"Model"}),e.jsxs("dd",{className:"font-mono text-xs",children:[t.model??t.defaultModel??"unknown",!t.model&&t.defaultModel&&e.jsx("span",{className:"ml-1 font-sans text-zinc-500",children:"(default)"})]})]}),t.quota&&e.jsxs(e.Fragment,{children:[e.jsxs("div",{children:[e.jsx("dt",{children:"Concurrency"}),e.jsx("dd",{children:t.quota.maxConcurrency})]}),e.jsxs("div",{children:[e.jsx("dt",{children:"Rate limit"}),e.jsxs("dd",{children:[t.quota.maxRequestsPerMinute,"/min · ",t.quota.maxRequestsPerDay,"/day"]})]})]})]}),e.jsx("p",{className:"mt-2 text-sm text-zinc-500",children:t.detail}),e.jsx("div",{className:"mt-2",children:e.jsx(f,{type:"button",variant:"outline",size:"sm",onClick:()=>x(o===t.name?null:t.name),children:o===t.name?"Cancel":t.state==="ready"?t.name.toLowerCase()==="local"?"Update config":"Update key":"Configure"})}),o===t.name&&e.jsx(J,{providerName:t.name,keyUrl:t.keyUrl,modelHint:t.modelHint,onSaved:()=>{x(null)}})]},t.name)),e.jsxs(b,{className:"surface-card",children:[e.jsxs("div",{className:"section-head",children:[e.jsxs("div",{children:[e.jsx("p",{className:"eyebrow eyebrow-soft",children:"Google"}),e.jsx("h2",{children:"Search Console OAuth"})]}),e.jsx(y,{tone:l.google.state==="ready"?"positive":"caution",children:l.google.state==="ready"?"Ready":"Needs config"})]}),e.jsxs("dl",{className:"definition-list mt-3",children:[e.jsxs("div",{children:[e.jsx("dt",{children:"Auth model"}),e.jsx("dd",{children:"One app credential set, then one OAuth connection per project domain"})]}),e.jsxs("div",{children:[e.jsx("dt",{children:"Storage"}),e.jsx("dd",{className:"font-mono text-xs",children:"~/.canonry/config.yaml"})]})]}),e.jsx("p",{className:"mt-2 text-sm text-zinc-500",children:l.google.detail}),e.jsx("div",{className:"mt-2",children:e.jsx(f,{type:"button",variant:"outline",size:"sm",onClick:()=>g(!d),children:d?"Cancel":l.google.state==="ready"?"Update OAuth app":"Configure Google OAuth"})}),d&&e.jsx(X,{onSaved:()=>{g(!1)}})]}),e.jsxs(b,{className:"surface-card",children:[e.jsxs("div",{className:"section-head",children:[e.jsxs("div",{children:[e.jsx("p",{className:"eyebrow eyebrow-soft",children:"Bing"}),e.jsx("h2",{children:"Webmaster Tools"})]}),e.jsx(y,{tone:l.bing.state==="ready"?"positive":"caution",children:l.bing.state==="ready"?"Ready":"Needs config"})]}),e.jsxs("dl",{className:"definition-list mt-3",children:[e.jsxs("div",{children:[e.jsx("dt",{children:"Auth model"}),e.jsx("dd",{children:"API key authentication — no OAuth needed"})]}),e.jsxs("div",{children:[e.jsx("dt",{children:"Storage"}),e.jsx("dd",{className:"font-mono text-xs",children:"~/.canonry/config.yaml"})]})]}),e.jsx("p",{className:"mt-2 text-sm text-zinc-500",children:l.bing.detail}),e.jsx("div",{className:"mt-2",children:e.jsx(f,{type:"button",variant:"outline",size:"sm",onClick:()=>p(!h),children:h?"Cancel":l.bing.state==="ready"?"Update API key":"Configure Bing"})}),h&&e.jsxs("div",{className:"mt-3 rounded-lg border border-zinc-800 bg-zinc-900/40 p-3 space-y-2",children:[e.jsxs("div",{children:[e.jsxs("div",{className:"flex items-center justify-between",children:[e.jsx("label",{className:"text-xs text-zinc-500",htmlFor:"bing-api-key",children:"API Key"}),e.jsx("a",{href:"https://www.bing.com/webmasters/",target:"_blank",rel:"noopener noreferrer",className:"text-[10px] text-zinc-500 hover:text-zinc-300 underline underline-offset-2",children:"Bing Webmaster Tools"})]}),e.jsx("input",{id:"bing-api-key",type:"password",className:"mt-0.5 w-full rounded border border-zinc-700 bg-transparent px-2 py-1.5 text-sm text-zinc-200 placeholder-zinc-600 focus:border-zinc-500 focus:outline-none",placeholder:"Bing Webmaster Tools API key",value:a,onChange:t=>i(t.target.value)})]}),e.jsxs("p",{className:"text-[11px] text-zinc-500",children:["This key is stored in ",e.jsx("code",{children:"~/.canonry/config.yaml"}),". Project-level Bing connections are created separately per canonical domain."]}),S&&e.jsx("p",{className:"text-xs text-rose-400",children:S}),P&&e.jsx("p",{className:"text-xs text-emerald-400",children:"Bing API key updated."}),e.jsx(f,{type:"button",size:"sm",disabled:!a.trim()||v,onClick:k(async()=>{if(a.trim()){N(!0),z(null),C(!1);try{await _(a.trim()),i(""),C(!0),p(!1),A({title:"Bing API key updated",detail:"Dashboard Bing credentials were saved.",tone:"positive",dedupeKey:"settings:bing",dedupeMode:"replace"})}catch(t){z(t instanceof Error?t.message:"Failed to update Bing API key")}finally{N(!1)}}}),children:v?"Saving...":"Save Bing API key"})]})]}),e.jsx(Y,{}),e.jsxs(b,{className:"surface-card",children:[e.jsx("div",{className:"section-head",children:e.jsxs("div",{children:[e.jsx("p",{className:"eyebrow eyebrow-soft",children:"Service health"}),e.jsx("h2",{children:"API and worker"})]})}),e.jsxs("div",{className:"compact-stack",children:[e.jsxs("div",{className:"health-row",children:[e.jsxs("div",{children:[e.jsx("p",{className:"run-row-title",children:"API"}),e.jsx("p",{className:"supporting-copy",children:c.apiStatus.detail})]}),e.jsx(y,{tone:T(c.apiStatus),title:F(c.apiStatus),children:c.apiStatus.state==="ok"?"Healthy":"Attention"})]}),e.jsxs("div",{className:"health-row",children:[e.jsxs("div",{children:[e.jsx("p",{className:"run-row-title",children:"Worker"}),e.jsx("p",{className:"supporting-copy",children:c.workerStatus.detail})]}),e.jsx(y,{tone:T(c.workerStatus),title:F(c.workerStatus),children:c.workerStatus.state==="ok"?"Healthy":"Attention"})]})]})]})]}),e.jsx("section",{className:"page-section",children:e.jsxs(b,{className:"surface-card",children:[e.jsx("div",{className:"section-head",children:e.jsxs("div",{children:[e.jsx("p",{className:"eyebrow eyebrow-soft",children:"Self-host notes"}),e.jsx("h2",{children:"Operational guidance"})]})}),e.jsx("ul",{className:"detail-list",children:l.selfHostNotes.map(t=>e.jsx("li",{children:t},t))}),e.jsx("p",{className:"supporting-copy",children:l.bootstrapNote})]})})]}):null}export{ie as SettingsPage};
@@ -1 +0,0 @@
1
- import{r as l,j as e,h as V,u as R,f as L,L as U}from"./vendor-tanstack-Be8JdV5M.js";import{c as T,b$ as E,c0 as $,c1 as I,c2 as q,c3 as A,c4 as B,B as z,i as O,c5 as H,bb as W,l as F,c6 as k,g as N,c7 as _,T as G,c8 as M}from"./index-54KuHvPl.js";import{a as J,b as Q,c as K,d as P,f as Z,t as Y,R as X}from"./extract-error-message-CgHHv6eH.js";import{A as ee}from"./arrow-left-DL0hZP8l.js";import"./vendor-radix-CQ77EO2y.js";import"./vendor-recharts-C9EZkgbP.js";import"./vendor-markdown-6dwjPOKK.js";const te=[["path",{d:"M17.5 19H9a7 7 0 1 1 6.71-9h1.79a4.5 4.5 0 1 1 0 9Z",key:"p7xjir"}]],ne=T("cloud",te);const re=[["path",{d:"M13.73 4a2 2 0 0 0-3.46 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3Z",key:"14u9p9"}]],se=T("triangle",re);function oe({open:t,onOpenChange:a,projectName:s}){const[r,n]=l.useState("pick");l.useEffect(()=>{t&&n("pick")},[t]);const o=()=>{a(!1),n("pick")};return e.jsx(E,{open:t,onOpenChange:i=>{i?a(!0):o()},children:e.jsx($,{children:r==="pick"?e.jsx(ie,{onPick:n}):r==="wordpress"?e.jsx(de,{projectName:s,onBack:()=>n("pick"),onClose:o}):r==="vercel"?e.jsx(pe,{projectName:s,onBack:()=>n("pick"),onClose:o}):e.jsx(ue,{projectName:s,onBack:()=>n("pick"),onClose:o})})})}const ae=[{type:"wordpress",name:"WordPress site",tagline:"Easiest if you run WordPress",description:"Install the Canonry Traffic Logger plugin and connect with an Application Password. No cloud account needed.",icon:B},{type:"cloud-run",name:"Google Cloud Run",tagline:"For apps hosted on Cloud Run",description:"Connect a Google Cloud service account so Canonry can read your Cloud Run request logs.",icon:ne},{type:"vercel",name:"Vercel project",tagline:"For sites hosted on Vercel",description:"Connect a Vercel personal access token so Canonry can pull request logs straight from Vercel, no in-app instrumentation needed.",icon:se}];function ie({onPick:t}){return e.jsxs(e.Fragment,{children:[e.jsxs(I,{children:[e.jsx(q,{children:"Connect a traffic source"}),e.jsx(A,{children:"Canonry reads your server logs to see when AI crawlers and AI-referred visitors hit your site. Pick where your site is hosted to get started."})]}),e.jsx("div",{className:"mt-6 flex flex-col gap-3",children:ae.map(({type:a,name:s,tagline:r,description:n,icon:o})=>e.jsxs("button",{type:"button",onClick:()=>t(a),className:"group flex items-start gap-4 rounded-lg border border-zinc-800 bg-zinc-900/30 p-4 text-left transition-colors hover:border-zinc-600 hover:bg-zinc-900/60 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-zinc-400",children:[e.jsx("span",{className:"mt-0.5 inline-flex size-9 shrink-0 items-center justify-center rounded-md border border-zinc-800 bg-zinc-950 text-zinc-300 group-hover:text-zinc-100",children:e.jsx(o,{className:"size-4"})}),e.jsxs("span",{className:"flex flex-col gap-0.5",children:[e.jsxs("span",{className:"flex flex-wrap items-baseline gap-x-2",children:[e.jsx("span",{className:"text-sm font-medium text-zinc-100",children:s}),e.jsx("span",{className:"text-[11px] text-zinc-500",children:r})]}),e.jsx("span",{className:"text-xs leading-5 text-zinc-500",children:n})]})]},a))})]})}function ce({title:t,description:a,onBack:s}){return e.jsxs(I,{children:[e.jsxs("button",{type:"button",onClick:s,className:"mb-1 inline-flex w-fit items-center gap-1 rounded text-xs text-zinc-500 transition-colors hover:text-zinc-200 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-zinc-400",children:[e.jsx(ee,{className:"size-3"}),"Choose a different source"]}),e.jsx(q,{children:t}),e.jsx(A,{children:a})]})}function le(t,a){const s=V();return async(r,n)=>{await H(t,r),n?.(),a(),s({to:"/traffic/$projectName/$sourceId",params:{projectName:t,sourceId:r}})}}function w(t,a){const[s,r]=l.useState(null),n=le(t,a);return{error:s,runConnect:async i=>{r(null);const d=i.validate();if(d){r(d);return}let u;try{u=await i.mutate()}catch(c){r(P(c));return}try{await n(u.id,i.onConnected)}catch(c){r(`Source connected, but starting the initial backfill failed: ${P(c)}`)}}}}function S({title:t,description:a,projectName:s,onBack:r,onClose:n,onSubmit:o,isPending:i,error:d,children:u}){return e.jsxs(e.Fragment,{children:[e.jsx(ce,{title:t,description:a,onBack:r}),e.jsxs("form",{onSubmit:O(async c=>{c.preventDefault(),await o()}),className:"mt-6 flex flex-col gap-5 overflow-y-auto pr-1",children:[e.jsx(p,{label:"Project",description:"Canonry project this source attaches to.",children:e.jsx("input",{type:"text",value:s,disabled:!0,className:"w-full rounded border border-zinc-700 bg-zinc-900/50 px-2 py-1.5 text-sm text-zinc-300"})}),u,d?e.jsx("p",{className:"rounded-md border border-rose-800/50 bg-rose-950/30 px-3 py-2 text-xs text-rose-200",children:d}):null,e.jsxs("div",{className:"mt-2 flex items-center justify-end gap-2 border-t border-zinc-800/60 pt-4",children:[e.jsx(z,{type:"button",variant:"ghost",size:"sm",onClick:n,children:"Close"}),e.jsx(z,{type:"submit",disabled:i,size:"sm",children:i?"Connecting…":"Connect"})]})]})]})}function de({projectName:t,onBack:a,onClose:s}){const[r,n]=l.useState(""),[o,i]=l.useState(""),[d,u]=l.useState(""),[c,b]=l.useState(""),f=J(t||null),{error:g,runConnect:j}=w(t,s),y=()=>j({validate:()=>r.trim()?o.trim()?d.trim()?null:"Application Password is required.":"Username is required.":"WordPress site URL is required.",mutate:()=>f.mutateAsync({baseUrl:r.trim(),username:o.trim(),applicationPassword:d.trim(),displayName:c.trim()||void 0}),onConnected:()=>u("")});return e.jsxs(S,{title:"Connect a WordPress site",description:e.jsxs(e.Fragment,{children:["Pulls request events from the Canonry Traffic Logger plugin. The Application Password is stored in ",e.jsx("code",{children:"~/.canonry/config.yaml"})," on the server and never echoed back to the dashboard."]}),projectName:t,onBack:a,onClose:s,onSubmit:y,isPending:f.isPending,error:g,children:[e.jsx(p,{label:"WordPress site URL",description:"Base URL of the site running the Canonry Traffic Logger plugin.",required:!0,children:e.jsx("input",{type:"url",value:r,onChange:h=>n(h.target.value),required:!0,autoComplete:"url",placeholder:"https://example.com",className:"w-full rounded border border-zinc-700 bg-transparent px-2 py-1.5 text-sm text-zinc-200 placeholder-zinc-600 focus:border-zinc-500 focus:outline-none"})}),e.jsx(p,{label:"Username",description:"WordPress user that owns the Application Password.",required:!0,children:e.jsx("input",{type:"text",value:o,onChange:h=>i(h.target.value),required:!0,autoComplete:"username",className:"w-full rounded border border-zinc-700 bg-transparent px-2 py-1.5 text-sm text-zinc-200 placeholder-zinc-600 focus:border-zinc-500 focus:outline-none"})}),e.jsx(p,{label:"Application Password",description:"Create one in wp-admin under Users -> Profile -> Application Passwords.",required:!0,children:e.jsx("input",{type:"password",value:d,onChange:h=>u(h.target.value),required:!0,autoComplete:"new-password",className:"w-full rounded border border-zinc-700 bg-transparent px-2 py-1.5 text-sm text-zinc-200 placeholder-zinc-600 focus:border-zinc-500 focus:outline-none"})}),e.jsx(p,{label:"Display name (optional)",description:"Friendly label shown in the dashboard. Defaults to the WordPress host.",children:e.jsx("input",{type:"text",value:c,onChange:h=>b(h.target.value),autoComplete:"off",className:"w-full rounded border border-zinc-700 bg-transparent px-2 py-1.5 text-sm text-zinc-200 placeholder-zinc-600 focus:border-zinc-500 focus:outline-none"})})]})}function ue({projectName:t,onBack:a,onClose:s}){const[r,n]=l.useState(""),[o,i]=l.useState(""),[d,u]=l.useState(""),[c,b]=l.useState(""),[f,g]=l.useState(""),j=K(t||null),{error:y,runConnect:h}=w(t,s),C=()=>h({validate:()=>r.trim()?f.trim()?null:"Service-account JSON content is required.":"GCP project ID is required.",mutate:()=>j.mutateAsync({gcpProjectId:r.trim(),serviceName:o.trim()||void 0,location:d.trim()||void 0,displayName:c.trim()||void 0,keyJson:f.trim()}),onConnected:()=>g("")}),m=async x=>{if(!x)return;const D=await x.text();g(D)};return e.jsxs(S,{title:"Connect a Cloud Run service",description:e.jsxs(e.Fragment,{children:["v1 supports service-account JSON only. The private key is stored in"," ",e.jsx("code",{children:"~/.canonry/config.yaml"})," on the server and never echoed back to the dashboard."]}),projectName:t,onBack:a,onClose:s,onSubmit:C,isPending:j.isPending,error:y,children:[e.jsx(p,{label:"GCP project ID",description:"The Google Cloud project hosting the Cloud Run service (e.g. my-prod-foo).",required:!0,children:e.jsx("input",{type:"text",value:r,onChange:x=>n(x.target.value),required:!0,autoComplete:"off",className:"w-full rounded border border-zinc-700 bg-transparent px-2 py-1.5 text-sm text-zinc-200 placeholder-zinc-600 focus:border-zinc-500 focus:outline-none"})}),e.jsx(p,{label:"Service name (optional)",description:"Restrict log pulls to a specific Cloud Run service. Omit to pull all services in the project.",children:e.jsx("input",{type:"text",value:o,onChange:x=>i(x.target.value),autoComplete:"off",className:"w-full rounded border border-zinc-700 bg-transparent px-2 py-1.5 text-sm text-zinc-200 placeholder-zinc-600 focus:border-zinc-500 focus:outline-none"})}),e.jsx(p,{label:"Location (optional)",description:"Region of the Cloud Run service (e.g. us-central1). Helpful when multiple regions emit logs.",children:e.jsx("input",{type:"text",value:d,onChange:x=>u(x.target.value),autoComplete:"off",className:"w-full rounded border border-zinc-700 bg-transparent px-2 py-1.5 text-sm text-zinc-200 placeholder-zinc-600 focus:border-zinc-500 focus:outline-none"})}),e.jsx(p,{label:"Display name (optional)",description:"Friendly label shown in the dashboard. Defaults to the project + service combo.",children:e.jsx("input",{type:"text",value:c,onChange:x=>b(x.target.value),autoComplete:"off",className:"w-full rounded border border-zinc-700 bg-transparent px-2 py-1.5 text-sm text-zinc-200 placeholder-zinc-600 focus:border-zinc-500 focus:outline-none"})}),e.jsxs(p,{label:"Service-account JSON",description:"Paste the contents of the SA key (JSON). The SA needs roles/logging.viewer (or any role granting logging.logEntries.list).",required:!0,children:[e.jsx("textarea",{value:f,onChange:x=>g(x.target.value),rows:6,spellCheck:!1,autoComplete:"off",className:"w-full rounded border border-zinc-700 bg-transparent px-2 py-1.5 font-mono text-[11px] text-zinc-200 placeholder-zinc-600 focus:border-zinc-500 focus:outline-none",placeholder:'{"type":"service_account","project_id":"…","private_key":"…"}',required:!0}),e.jsxs("label",{className:"mt-2 inline-flex cursor-pointer items-center gap-2 text-xs text-zinc-400 hover:text-zinc-200",children:[e.jsx("input",{type:"file",accept:"application/json,.json",className:"hidden",onChange:x=>{m(x.target.files?.[0]??null)}}),e.jsx("span",{className:"rounded-md border border-zinc-800 px-2 py-1",children:"Or upload a key file"})]})]})]})}function pe({projectName:t,onBack:a,onClose:s}){const[r,n]=l.useState(""),[o,i]=l.useState(""),[d,u]=l.useState(""),[c,b]=l.useState("production"),[f,g]=l.useState(""),j=Q(t||null),{error:y,runConnect:h}=w(t,s),C=()=>h({validate:()=>r.trim()?o.trim()?d.trim()?null:"Vercel personal access token is required.":"Vercel team / account ID is required.":"Vercel project ID is required.",mutate:()=>j.mutateAsync({projectId:r.trim(),teamId:o.trim(),token:d.trim(),environment:c,displayName:f.trim()||void 0}),onConnected:()=>u("")});return e.jsxs(S,{title:"Connect a Vercel project",description:e.jsxs(e.Fragment,{children:["Pulls request logs straight from Vercel, no in-app instrumentation needed. The personal access token is stored in ",e.jsx("code",{children:"~/.canonry/config.yaml"})," on the server and never echoed back to the dashboard."]}),projectName:t,onBack:a,onClose:s,onSubmit:C,isPending:j.isPending,error:y,children:[e.jsx(p,{label:"Vercel project ID",description:"The prj_… id from the Vercel dashboard or .vercel/project.json.",required:!0,children:e.jsx("input",{type:"text",value:r,onChange:m=>n(m.target.value),required:!0,autoComplete:"off",placeholder:"prj_…",className:"w-full rounded border border-zinc-700 bg-transparent px-2 py-1.5 text-sm text-zinc-200 placeholder-zinc-600 focus:border-zinc-500 focus:outline-none"})}),e.jsx(p,{label:"Vercel team / account ID",description:"The Vercel team or personal account that owns the project. Find it as orgId in your .vercel/project.json.",required:!0,children:e.jsx("input",{type:"text",value:o,onChange:m=>i(m.target.value),required:!0,autoComplete:"off",className:"w-full rounded border border-zinc-700 bg-transparent px-2 py-1.5 text-sm text-zinc-200 placeholder-zinc-600 focus:border-zinc-500 focus:outline-none"})}),e.jsx(p,{label:"Personal access token",description:"Create a Vercel personal access token under Account Settings → Tokens. Tokens can expire, so use a long-lived one.",required:!0,children:e.jsx("input",{type:"password",value:d,onChange:m=>u(m.target.value),required:!0,autoComplete:"new-password",className:"w-full rounded border border-zinc-700 bg-transparent px-2 py-1.5 text-sm text-zinc-200 placeholder-zinc-600 focus:border-zinc-500 focus:outline-none"})}),e.jsx(p,{label:"Environment",description:"Which deployment environment's request logs to pull.",children:e.jsxs("select",{value:c,onChange:m=>b(m.target.value),className:"w-full rounded border border-zinc-700 bg-transparent px-2 py-1.5 text-sm text-zinc-200 focus:border-zinc-500 focus:outline-none",children:[e.jsx("option",{value:"production",children:"production"}),e.jsx("option",{value:"preview",children:"preview"})]})}),e.jsx(p,{label:"Display name (optional)",description:"Friendly label shown in the dashboard. Defaults to the Vercel project ID.",children:e.jsx("input",{type:"text",value:f,onChange:m=>g(m.target.value),autoComplete:"off",className:"w-full rounded border border-zinc-700 bg-transparent px-2 py-1.5 text-sm text-zinc-200 placeholder-zinc-600 focus:border-zinc-500 focus:outline-none"})})]})}function p({label:t,description:a,required:s,children:r}){return e.jsxs("label",{className:"flex flex-col gap-1",children:[e.jsxs("span",{className:"text-xs font-medium text-zinc-200",children:[t,s?e.jsx("span",{className:"ml-1 text-rose-400",children:"*"}):null]}),r,e.jsx("span",{className:"text-[11px] text-zinc-500",children:a})]})}function xe(t){if(!t)return"never";const a=Date.now()-new Date(t).getTime(),s=Math.floor(a/6e4);if(s<1)return"just now";if(s<60)return`${s}m ago`;const r=Math.floor(s/60);return r<24?`${r}h ago`:`${Math.floor(r/24)}d ago`}function v(t){return t>=1e6?`${(t/1e6).toFixed(1)}M`:t>=1e3?`${(t/1e3).toFixed(1)}K`:t.toLocaleString()}function ze(){const[t,a]=l.useState(""),[s,r]=l.useState(!1),o=R(W({client:F})).data??[],i=l.useMemo(()=>t||(o[0]?.name??""),[t,o]),d=Z(i||null),u=d.data?.sources??[];return e.jsxs("div",{className:"page-container",children:[e.jsxs("div",{className:"page-header",children:[e.jsxs("div",{className:"page-header-left",children:[e.jsx("h1",{className:"page-title",children:"Server traffic"}),e.jsx("p",{className:"page-subtitle",children:"Bulk crawler hits, AI user fetches (ChatGPT-User, Perplexity-User), and AI referral sessions pulled directly from your server logs. Independent of GA — useful when you need server-side evidence that an AI engine actually hit a page."})]}),e.jsx("div",{className:"flex flex-wrap items-center justify-end gap-2",children:e.jsxs(z,{type:"button",variant:"outline",size:"sm",onClick:()=>r(!0),disabled:!i,children:[e.jsx(k,{className:"size-3.5"}),"Connect a source"]})})]}),e.jsxs("section",{children:[e.jsx("div",{className:"filter-row",role:"toolbar","aria-label":"Project picker",children:o.map(c=>e.jsx("button",{type:"button",className:`filter-chip ${i===c.name?"filter-chip-active":""}`,"aria-pressed":i===c.name,onClick:()=>a(c.name),children:c.displayName??c.name},c.id))}),i?d.isLoading?e.jsx(N,{className:"p-6 text-center text-sm text-zinc-500",children:"Loading sources…"}):u.length===0?e.jsxs(N,{className:"p-8 text-center",children:[e.jsxs("p",{className:"text-sm text-zinc-300",children:["No traffic sources connected for ",i,"."]}),e.jsx("p",{className:"mt-1 text-xs text-zinc-500",children:"Connect a traffic source to start ingesting crawler hits and AI-referral sessions from your server logs."}),e.jsx("div",{className:"mt-4 flex flex-wrap items-center justify-center gap-2",children:e.jsxs(z,{type:"button",variant:"outline",size:"sm",onClick:()=>r(!0),children:[e.jsx(k,{className:"size-3.5"}),"Connect a source"]})})]}):e.jsx(he,{projectName:i,sources:u}):e.jsx(N,{className:"p-6 text-center text-sm text-zinc-500",children:"No projects yet."})]}),e.jsx(oe,{open:s,onOpenChange:r,projectName:i})]})}function he({projectName:t,sources:a}){const s=L({queries:a.map(n=>({..._({client:F,path:{name:t,id:n.id}}),staleTime:3e4}))}),r=a.map((n,o)=>({source:n,detail:s[o]?.data,isLoading:s[o]?.isLoading??!1}));return e.jsxs("div",{className:"rounded-xl border border-zinc-800/60 bg-zinc-900/30 overflow-hidden",children:[e.jsxs("table",{className:"w-full text-sm",children:[e.jsx("thead",{className:"bg-zinc-900/50 text-[10px] font-semibold uppercase tracking-wider text-zinc-500",children:e.jsxs("tr",{children:[e.jsx("th",{className:"px-4 py-2 text-left",children:"Source"}),e.jsx("th",{className:"px-4 py-2 text-left",children:"Status"}),e.jsx("th",{className:"px-4 py-2 text-left",children:"Last sync"}),e.jsx("th",{className:"px-4 py-2 text-right",children:"24h content"}),e.jsx("th",{className:"px-4 py-2 text-right",children:"24h infra"}),e.jsx("th",{className:"px-4 py-2 text-right",children:"24h AI hits"}),e.jsx("th",{className:"px-4 py-2 text-right",children:"24h AI sessions"}),e.jsx("th",{className:"px-4 py-2 text-right"})]})}),e.jsx("tbody",{className:"divide-y divide-zinc-800/60",children:r.map(({source:n,detail:o,isLoading:i})=>e.jsxs("tr",{className:"hover:bg-zinc-900/40 transition-colors",children:[e.jsxs("td",{className:"px-4 py-3",children:[e.jsx("div",{className:"font-medium text-zinc-100",children:n.displayName}),e.jsxs("div",{className:"text-[11px] text-zinc-500 font-mono",children:[n.sourceType," · ",n.id.slice(0,8)]})]}),e.jsxs("td",{className:"px-4 py-3",children:[e.jsx(G,{tone:Y(n.status),children:n.status}),n.lastError?e.jsx("p",{className:"mt-1 max-w-[18rem] truncate text-[11px] text-rose-400/80",title:n.lastError,children:n.lastError}):null]}),e.jsx("td",{className:"px-4 py-3 text-zinc-300",children:xe(n.lastSyncedAt)}),e.jsx("td",{className:"px-4 py-3 text-right tabular-nums text-zinc-100",title:o?`${o.totals24h.crawlerHits.toLocaleString("en-US")} total crawler hits`:void 0,children:i?"—":v(o?.totals24h.crawlerContentHits??0)}),e.jsx("td",{className:"px-4 py-3 text-right tabular-nums text-zinc-500",children:i?"—":v(o?.totals24h.crawlerInfraHits??0)}),e.jsx("td",{className:"px-4 py-3 text-right tabular-nums text-zinc-100",children:i?"—":v(o?.totals24h.aiUserFetchHits??0)}),e.jsx("td",{className:"px-4 py-3 text-right tabular-nums text-zinc-100",children:i?"—":v(o?.totals24h.aiReferralHits??0)}),e.jsx("td",{className:"px-4 py-3 text-right",children:e.jsxs(U,{to:"/traffic/$projectName/$sourceId",params:{projectName:t,sourceId:n.id},className:"inline-flex items-center gap-1 text-xs text-zinc-300 hover:text-zinc-100",children:[e.jsx(X,{className:"size-3"}),"View"]})})]},n.id))})]}),e.jsxs("p",{className:"border-t border-zinc-800/60 px-4 py-2 text-[11px] text-zinc-600",children:["Showing ",a.filter(n=>n.status!==M.archived).length," active source",a.length===1?"":"s"," for ",t,". Same shape as ",e.jsxs("code",{className:"text-zinc-400",children:["canonry traffic status ",t," --format json"]}),"."]})]})}export{ze as TrafficPage};