@ainyc/canonry 4.108.0 → 4.110.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.
- package/assets/assets/{BacklinksPage-Dr6MK4h1.js → BacklinksPage-Duk8YbV2.js} +1 -1
- package/assets/assets/{ChartPrimitives-DGO0EGqM.js → ChartPrimitives-aLxtVH28.js} +1 -1
- package/assets/assets/{ProjectPage-fHczny77.js → ProjectPage-Bqd0JvpC.js} +1 -1
- package/assets/assets/{RunRow-Br6uOqD1.js → RunRow-DH8UMAwj.js} +1 -1
- package/assets/assets/{RunsPage-MMEhx2vt.js → RunsPage-OPteH1b_.js} +1 -1
- package/assets/assets/SettingsPage-7cspMOFv.js +1 -0
- package/assets/assets/TrafficPage-D4pcnfc8.js +1 -0
- package/assets/assets/{TrafficSourceDetailPage-DFFGJsVC.js → TrafficSourceDetailPage-D-KbCCEK.js} +1 -1
- package/assets/assets/{arrow-left-DDnYTDM6.js → arrow-left-M8KVuve8.js} +1 -1
- package/assets/assets/{extract-error-message-B0KSa98X.js → extract-error-message-D2aTaSV5.js} +1 -1
- package/assets/assets/{index-imKX6Edq.js → index-ChPgQUZ5.js} +61 -61
- package/assets/assets/index-CjrSrPKf.css +1 -0
- package/assets/assets/{trash-2-BODtOdNo.js → trash-2-BNVgf5NY.js} +1 -1
- package/assets/index.html +2 -2
- package/dist/{chunk-EMG4LIUJ.js → chunk-6PHBWPPN.js} +39 -19
- package/dist/{chunk-45YSA7GH.js → chunk-JGRVQJHX.js} +69 -7
- package/dist/{chunk-EKGKGKID.js → chunk-X4LLTNQZ.js} +2 -1
- package/dist/{chunk-IQX7XXJN.js → chunk-YQ4KOC5Z.js} +52 -0
- package/dist/cli.js +13 -8
- package/dist/index.js +4 -4
- package/dist/{intelligence-service-YDPWS7OC.js → intelligence-service-BGLF7DQW.js} +2 -2
- package/dist/mcp.js +2 -2
- package/package.json +6 -6
- package/assets/assets/SettingsPage-BjZZODA1.js +0 -1
- package/assets/assets/TrafficPage-BtMtH6FT.js +0 -1
- package/assets/assets/index-DtsuPsZB.css +0 -1
|
@@ -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,
|
|
@@ -269,7 +270,7 @@ import {
|
|
|
269
270
|
wordpressSchemaDeployResultDtoSchema,
|
|
270
271
|
wordpressSchemaStatusResultDtoSchema,
|
|
271
272
|
wordpressStatusDtoSchema
|
|
272
|
-
} from "./chunk-
|
|
273
|
+
} from "./chunk-YQ4KOC5Z.js";
|
|
273
274
|
|
|
274
275
|
// src/intelligence-service.ts
|
|
275
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";
|
|
@@ -1152,6 +1153,17 @@ var discoverySessions = sqliteTable("discovery_sessions", {
|
|
|
1152
1153
|
// Resolved service areas the session was seeded/probed with; part of session
|
|
1153
1154
|
// identity for in-flight consolidation. Null on legacy sessions.
|
|
1154
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"),
|
|
1163
|
+
// Seed provider set (canonical order; null = legacy / Gemini-only) — part of
|
|
1164
|
+
// session identity for consolidation — and per-provider candidate counts.
|
|
1165
|
+
seedProviders: text("seed_providers", { mode: "json" }).$type(),
|
|
1166
|
+
seedProviderCounts: text("seed_provider_counts", { mode: "json" }).$type(),
|
|
1155
1167
|
dedupThreshold: real("dedup_threshold"),
|
|
1156
1168
|
probeCount: integer("probe_count"),
|
|
1157
1169
|
citedCount: integer("cited_count"),
|
|
@@ -3432,6 +3444,28 @@ var MIGRATION_VERSIONS = [
|
|
|
3432
3444
|
// legacy sessions stay null.
|
|
3433
3445
|
`ALTER TABLE discovery_sessions ADD COLUMN locations TEXT`
|
|
3434
3446
|
]
|
|
3447
|
+
},
|
|
3448
|
+
{
|
|
3449
|
+
version: 92,
|
|
3450
|
+
name: "discovery-session-dedup-diagnostics",
|
|
3451
|
+
statements: [
|
|
3452
|
+
// Seed provenance + dedup calibration diagnostics. Additive + nullable —
|
|
3453
|
+
// legacy sessions stay null; old writers omit them (downgrade-safe).
|
|
3454
|
+
`ALTER TABLE discovery_sessions ADD COLUMN seed_raw_candidates TEXT`,
|
|
3455
|
+
`ALTER TABLE discovery_sessions ADD COLUMN dedup_cluster_min_sims TEXT`,
|
|
3456
|
+
`ALTER TABLE discovery_sessions ADD COLUMN dedup_band_pair_fraction REAL`,
|
|
3457
|
+
`ALTER TABLE discovery_sessions ADD COLUMN dedup_pairs_total INTEGER`
|
|
3458
|
+
]
|
|
3459
|
+
},
|
|
3460
|
+
{
|
|
3461
|
+
version: 93,
|
|
3462
|
+
name: "discovery-session-seed-providers",
|
|
3463
|
+
statements: [
|
|
3464
|
+
// Seed provider set (consolidation identity) + per-provider candidate
|
|
3465
|
+
// counts. Additive + nullable — legacy rows stay null (downgrade-safe).
|
|
3466
|
+
`ALTER TABLE discovery_sessions ADD COLUMN seed_providers TEXT`,
|
|
3467
|
+
`ALTER TABLE discovery_sessions ADD COLUMN seed_provider_counts TEXT`
|
|
3468
|
+
]
|
|
3435
3469
|
}
|
|
3436
3470
|
];
|
|
3437
3471
|
function rebuildBacklinkTableWithSource(tx, table) {
|
|
@@ -18099,6 +18133,7 @@ var routeCatalog = [
|
|
|
18099
18133
|
properties: {
|
|
18100
18134
|
icpDescription: { type: "string", description: "Free-text ICP. Required if the project does not have spec.icpDescription stored." },
|
|
18101
18135
|
buyerDescription: { type: "string", description: "Who evaluates or buys the offering, separate from the ICP. When present, every generated seed query is anchored on this buyer and the seed prompt enforces buyer-fit." },
|
|
18136
|
+
seedProviders: { type: "array", items: { type: "string", enum: ["gemini", "openai"] }, minItems: 1, description: "Which providers generate seed candidates. Omit for the Gemini-only default. Canonicalized (deduped + sorted); part of the session consolidation identity." },
|
|
18102
18137
|
dedupThreshold: { type: "number", description: "Cosine similarity threshold for clustering. Defaults to 0.95." },
|
|
18103
18138
|
maxProbes: { type: "integer", description: "Max canonical queries to probe in this session. Default 100, hard cap 500." },
|
|
18104
18139
|
probeConcurrency: {
|
|
@@ -33792,6 +33827,8 @@ async function discoveryRoutes(app, opts) {
|
|
|
33792
33827
|
"icpDescription is required. Pass it in the request body or store it on the project (spec.icpDescription)."
|
|
33793
33828
|
);
|
|
33794
33829
|
}
|
|
33830
|
+
const requestedSeedProviders = parsed.data.seedProviders ?? null;
|
|
33831
|
+
const seedProviders = requestedSeedProviders && !(requestedSeedProviders.length === 1 && requestedSeedProviders[0] === "gemini") ? requestedSeedProviders : null;
|
|
33795
33832
|
const locations = orderLocationsDefaultFirst(
|
|
33796
33833
|
resolveLocations(project.locations, parsed.data.locations),
|
|
33797
33834
|
project.defaultLocation
|
|
@@ -33821,6 +33858,10 @@ async function discoveryRoutes(app, opts) {
|
|
|
33821
33858
|
// never reuses — a one-time, bounded (2h window) non-reuse after
|
|
33822
33859
|
// upgrade, never a wrong reuse.
|
|
33823
33860
|
locations.length === 0 ? or5(isNull(discoverySessions.locations), eq35(discoverySessions.locations, locations)) : eq35(discoverySessions.locations, locations),
|
|
33861
|
+
// Seed provider set is identity: a different phrasing distribution
|
|
33862
|
+
// must never reuse another set's session. Null (= Gemini-only
|
|
33863
|
+
// default, including explicit ['gemini']) matches legacy rows.
|
|
33864
|
+
seedProviders == null ? isNull(discoverySessions.seedProviders) : eq35(discoverySessions.seedProviders, seedProviders),
|
|
33824
33865
|
inArray12(discoverySessions.status, [
|
|
33825
33866
|
DiscoverySessionStatuses.queued,
|
|
33826
33867
|
DiscoverySessionStatuses.seeding,
|
|
@@ -33840,6 +33881,7 @@ async function discoveryRoutes(app, opts) {
|
|
|
33840
33881
|
status: DiscoverySessionStatuses.queued,
|
|
33841
33882
|
icpDescription,
|
|
33842
33883
|
buyerDescription: parsed.data.buyerDescription ?? null,
|
|
33884
|
+
seedProviders,
|
|
33843
33885
|
locations,
|
|
33844
33886
|
dedupThreshold: parsed.data.dedupThreshold,
|
|
33845
33887
|
competitorMap: [],
|
|
@@ -33876,6 +33918,7 @@ async function discoveryRoutes(app, opts) {
|
|
|
33876
33918
|
projectId: project.id,
|
|
33877
33919
|
icpDescription,
|
|
33878
33920
|
buyerDescription: parsed.data.buyerDescription,
|
|
33921
|
+
seedProviders: seedProviders ?? void 0,
|
|
33879
33922
|
dedupThreshold: parsed.data.dedupThreshold,
|
|
33880
33923
|
maxProbes: parsed.data.maxProbes,
|
|
33881
33924
|
probeConcurrency: parsed.data.probeConcurrency,
|
|
@@ -34133,6 +34176,12 @@ function serializeSession(row) {
|
|
|
34133
34176
|
seedBrandFilteredCount: row.seedBrandFilteredCount ?? null,
|
|
34134
34177
|
buyerDescription: row.buyerDescription ?? null,
|
|
34135
34178
|
locations: row.locations ?? null,
|
|
34179
|
+
seedRawCandidates: row.seedRawCandidates ?? null,
|
|
34180
|
+
seedProviders: row.seedProviders ?? null,
|
|
34181
|
+
seedProviderCounts: row.seedProviderCounts ?? null,
|
|
34182
|
+
dedupClusterMinSims: row.dedupClusterMinSims ?? null,
|
|
34183
|
+
dedupBandPairFraction: row.dedupBandPairFraction ?? null,
|
|
34184
|
+
dedupPairsTotal: row.dedupPairsTotal ?? null,
|
|
34136
34185
|
dedupThreshold: row.dedupThreshold ?? null,
|
|
34137
34186
|
probeCount: row.probeCount ?? null,
|
|
34138
34187
|
citedCount: row.citedCount ?? null,
|
|
@@ -34215,12 +34264,14 @@ async function classifyCompetitorDomains(deps, project, icpDescription, domains)
|
|
|
34215
34264
|
return {};
|
|
34216
34265
|
}
|
|
34217
34266
|
}
|
|
34218
|
-
async function
|
|
34219
|
-
|
|
34220
|
-
if (candidates.length ===
|
|
34267
|
+
async function pickCanonicalsWithStats(candidates, deps, dedupThreshold) {
|
|
34268
|
+
const emptyStats = { perClusterMinSimilarity: [], bandPairFraction: null, pairsTotal: 0 };
|
|
34269
|
+
if (candidates.length === 0) return { canonicals: [], stats: emptyStats };
|
|
34270
|
+
if (candidates.length === 1) return { canonicals: candidates, stats: emptyStats };
|
|
34221
34271
|
const vectors = await deps.embed(candidates);
|
|
34222
|
-
const
|
|
34223
|
-
|
|
34272
|
+
const indexClusters = clusterByCosine(candidates.map((_, i) => i), vectors, dedupThreshold);
|
|
34273
|
+
const canonicals = indexClusters.map((cluster) => pickClusterRepresentative(cluster.map((i) => candidates[i])));
|
|
34274
|
+
return { canonicals, stats: computeDedupSimilarityStats(vectors, indexClusters) };
|
|
34224
34275
|
}
|
|
34225
34276
|
async function executeDiscovery(opts) {
|
|
34226
34277
|
const dedupThreshold = opts.dedupThreshold ?? DISCOVERY_DEFAULT_DEDUP_THRESHOLD;
|
|
@@ -34240,6 +34291,7 @@ async function executeDiscovery(opts) {
|
|
|
34240
34291
|
project: opts.project,
|
|
34241
34292
|
icpDescription: opts.icpDescription,
|
|
34242
34293
|
buyerDescription: opts.buyerDescription,
|
|
34294
|
+
seedProviders: opts.seedProviders,
|
|
34243
34295
|
locations: opts.locations ?? []
|
|
34244
34296
|
});
|
|
34245
34297
|
const { kept: unbrandedCandidates, droppedBranded } = filterBrandedSeedCandidates({
|
|
@@ -34249,7 +34301,7 @@ async function executeDiscovery(opts) {
|
|
|
34249
34301
|
});
|
|
34250
34302
|
const rawCandidates = dedupeStrings(unbrandedCandidates);
|
|
34251
34303
|
const seedCountRaw = rawCandidates.length;
|
|
34252
|
-
const canonicals = await
|
|
34304
|
+
const { canonicals, stats: dedupStats } = await pickCanonicalsWithStats(
|
|
34253
34305
|
rawCandidates,
|
|
34254
34306
|
{ embed: opts.deps.embed },
|
|
34255
34307
|
dedupThreshold
|
|
@@ -34271,6 +34323,16 @@ async function executeDiscovery(opts) {
|
|
|
34271
34323
|
seedFromAnswerCount: seedResult.fromAnswerCount ?? null,
|
|
34272
34324
|
seedFromGroundingCount: seedResult.fromGroundingCount ?? null,
|
|
34273
34325
|
seedBrandFilteredCount: droppedBranded.length,
|
|
34326
|
+
// Full seed provenance + dedup calibration diagnostics: the raw
|
|
34327
|
+
// candidate list (as the seed dep returned it, pre-filter) makes every
|
|
34328
|
+
// live session a replayable fixture; the similarity stats are the data
|
|
34329
|
+
// the 0.95-threshold decision was missing. No gate reads any of these.
|
|
34330
|
+
seedRawCandidates: seedResult.candidates,
|
|
34331
|
+
seedProviders: opts.seedProviders ?? null,
|
|
34332
|
+
seedProviderCounts: seedResult.providerCounts ?? null,
|
|
34333
|
+
dedupClusterMinSims: dedupStats.perClusterMinSimilarity,
|
|
34334
|
+
dedupBandPairFraction: dedupStats.bandPairFraction,
|
|
34335
|
+
dedupPairsTotal: dedupStats.pairsTotal,
|
|
34274
34336
|
warning
|
|
34275
34337
|
}).where(eq36(discoverySessions.id, opts.sessionId)).run();
|
|
34276
34338
|
const probeLocation = opts.locations?.[0];
|
|
@@ -25,7 +25,7 @@ import {
|
|
|
25
25
|
trafficConnectVercelRequestSchema,
|
|
26
26
|
trafficConnectWordpressRequestSchema,
|
|
27
27
|
trafficEventKindSchema
|
|
28
|
-
} from "./chunk-
|
|
28
|
+
} from "./chunk-YQ4KOC5Z.js";
|
|
29
29
|
|
|
30
30
|
// src/config.ts
|
|
31
31
|
import fs from "fs";
|
|
@@ -5566,6 +5566,7 @@ var discoveryRunInputSchema = z2.object({
|
|
|
5566
5566
|
// upper bound; this just clarifies the meaning of each knob.
|
|
5567
5567
|
icpDescription: z2.string().min(1).optional().describe("Free-text ICP description. If omitted, the project must already have spec.icpDescription stored."),
|
|
5568
5568
|
buyerDescription: z2.string().min(1).optional().describe("Who evaluates or buys the offering, separate from the ICP. When present, every generated query is anchored on this buyer."),
|
|
5569
|
+
seedProviders: z2.array(z2.enum(["gemini", "openai"])).min(1).optional().describe('Which providers generate seed candidates. Omit for the Gemini-only default; ["gemini","openai"] merges both phrasing distributions before dedup.'),
|
|
5569
5570
|
dedupThreshold: z2.number().min(0).max(1).optional().describe("Cosine similarity threshold for clustering seed candidates. Defaults to 0.85. Lower values dedupe more aggressively."),
|
|
5570
5571
|
maxProbes: z2.number().int().positive().max(DISCOVERY_MAX_PROBES_CAP).optional().describe(`Max canonical queries to probe in this session. Default 100, hard cap ${DISCOVERY_MAX_PROBES_CAP}.`),
|
|
5571
5572
|
probeConcurrency: z2.number().int().min(1).max(DISCOVERY_PROBE_CONCURRENCY_CAP).optional().describe(`How many probes may run in parallel. Default 1 (strictly serial), hard cap ${DISCOVERY_PROBE_CONCURRENCY_CAP}. Probe rows are persisted in canonical order regardless of concurrency, so this only shortens wall-clock time.`)
|
|
@@ -1993,6 +1993,34 @@ function pickClusterRepresentative(cluster) {
|
|
|
1993
1993
|
}
|
|
1994
1994
|
return best;
|
|
1995
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
|
+
}
|
|
1996
2024
|
|
|
1997
2025
|
// ../contracts/src/url-normalize.ts
|
|
1998
2026
|
var STRIP_KEYS = /* @__PURE__ */ new Set([
|
|
@@ -2205,6 +2233,19 @@ var discoverySessionDtoSchema = z17.object({
|
|
|
2205
2233
|
/** Resolved service areas the session was seeded/probed with (part of
|
|
2206
2234
|
* session identity for in-flight consolidation). Null on legacy sessions. */
|
|
2207
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(),
|
|
2244
|
+
/** Seed provider set the session ran with (canonical order); null = legacy /
|
|
2245
|
+
* Gemini-only default. */
|
|
2246
|
+
seedProviders: z17.array(z17.string()).nullable().optional(),
|
|
2247
|
+
/** Raw candidate count contributed per seed provider. */
|
|
2248
|
+
seedProviderCounts: z17.record(z17.string(), z17.number().int()).nullable().optional(),
|
|
2208
2249
|
dedupThreshold: z17.number().nullable().optional(),
|
|
2209
2250
|
probeCount: z17.number().int().nullable().optional(),
|
|
2210
2251
|
citedCount: z17.number().int().nullable().default(null),
|
|
@@ -2281,6 +2322,8 @@ function containsWholeToken(normalized, token) {
|
|
|
2281
2322
|
}
|
|
2282
2323
|
return false;
|
|
2283
2324
|
}
|
|
2325
|
+
var DISCOVERY_SEED_PROVIDERS = ["gemini", "openai"];
|
|
2326
|
+
var discoverySeedProviderSchema = z17.enum(DISCOVERY_SEED_PROVIDERS);
|
|
2284
2327
|
var discoveryRunRequestSchema = z17.object({
|
|
2285
2328
|
icpDescription: z17.string().min(1).optional(),
|
|
2286
2329
|
/**
|
|
@@ -2290,6 +2333,14 @@ var discoveryRunRequestSchema = z17.object({
|
|
|
2290
2333
|
* generic provider comparisons.
|
|
2291
2334
|
*/
|
|
2292
2335
|
buyerDescription: z17.string().min(1).optional(),
|
|
2336
|
+
/**
|
|
2337
|
+
* Which providers generate seed candidates. Omitted = Gemini-only (the
|
|
2338
|
+
* historical behaviour). Canonicalized (deduped + sorted) so the value is
|
|
2339
|
+
* stable for session-identity comparison. Part of the consolidation
|
|
2340
|
+
* IDENTITY: a different provider set produces a different phrasing
|
|
2341
|
+
* distribution, so it must never reuse another set's session.
|
|
2342
|
+
*/
|
|
2343
|
+
seedProviders: z17.array(discoverySeedProviderSchema).min(1).transform((arr) => [...new Set(arr)].sort()).optional(),
|
|
2293
2344
|
dedupThreshold: z17.number().min(0).max(1).optional(),
|
|
2294
2345
|
maxProbes: z17.number().int().positive().max(DISCOVERY_MAX_PROBES_CAP).optional(),
|
|
2295
2346
|
/**
|
|
@@ -5711,6 +5762,7 @@ export {
|
|
|
5711
5762
|
categoryLabel,
|
|
5712
5763
|
clusterByCosine,
|
|
5713
5764
|
pickClusterRepresentative,
|
|
5765
|
+
computeDedupSimilarityStats,
|
|
5714
5766
|
absolutizeProjectUrl,
|
|
5715
5767
|
hostOf,
|
|
5716
5768
|
normalizeUrlPath,
|
package/dist/cli.js
CHANGED
|
@@ -29,7 +29,7 @@ import {
|
|
|
29
29
|
setTelemetrySource,
|
|
30
30
|
showFirstRunNotice,
|
|
31
31
|
trackEvent
|
|
32
|
-
} from "./chunk-
|
|
32
|
+
} from "./chunk-6PHBWPPN.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-
|
|
49
|
+
} from "./chunk-X4LLTNQZ.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-
|
|
57
|
+
} from "./chunk-JGRVQJHX.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-
|
|
83
|
+
} from "./chunk-YQ4KOC5Z.js";
|
|
84
84
|
|
|
85
85
|
// src/cli.ts
|
|
86
86
|
import { pathToFileURL } from "url";
|
|
@@ -1656,6 +1656,7 @@ function buildRunBody(opts, icpDescription) {
|
|
|
1656
1656
|
const body = {};
|
|
1657
1657
|
if (icpDescription) body.icpDescription = icpDescription;
|
|
1658
1658
|
if (opts.buyer !== void 0 && opts.buyer.trim() !== "") body.buyerDescription = opts.buyer;
|
|
1659
|
+
if (opts.seedProviders && opts.seedProviders.length > 0) body.seedProviders = opts.seedProviders;
|
|
1659
1660
|
if (opts.dedupThreshold !== void 0) body.dedupThreshold = opts.dedupThreshold;
|
|
1660
1661
|
if (opts.maxProbes !== void 0) body.maxProbes = opts.maxProbes;
|
|
1661
1662
|
if (opts.probeConcurrency !== void 0) body.probeConcurrency = opts.probeConcurrency;
|
|
@@ -2058,10 +2059,11 @@ Usage: ${usage}`,
|
|
|
2058
2059
|
var DISCOVER_CLI_COMMANDS = [
|
|
2059
2060
|
{
|
|
2060
2061
|
path: ["discover", "run"],
|
|
2061
|
-
usage: 'canonry discover run <project> [--icp "..."] [--buyer "..."] [--icp-angle "..."] [--locations michigan,florida] [--dedup-threshold 0.95] [--max-probes 100] [--probe-concurrency 3] [--wait] [--format json]',
|
|
2062
|
+
usage: 'canonry discover run <project> [--icp "..."] [--buyer "..."] [--seed-provider gemini --seed-provider openai] [--icp-angle "..."] [--locations michigan,florida] [--dedup-threshold 0.95] [--max-probes 100] [--probe-concurrency 3] [--wait] [--format json]',
|
|
2062
2063
|
options: {
|
|
2063
2064
|
icp: stringOption(),
|
|
2064
2065
|
buyer: stringOption(),
|
|
2066
|
+
"seed-provider": multiStringOption(),
|
|
2065
2067
|
"icp-angle": multiStringOption(),
|
|
2066
2068
|
locations: multiStringOption(),
|
|
2067
2069
|
"dedup-threshold": stringOption(),
|
|
@@ -2070,11 +2072,12 @@ var DISCOVER_CLI_COMMANDS = [
|
|
|
2070
2072
|
wait: { type: "boolean", default: false }
|
|
2071
2073
|
},
|
|
2072
2074
|
run: async (input) => {
|
|
2073
|
-
const usage = 'canonry discover run <project> [--icp "..."] [--buyer "..."] [--icp-angle "..."] [--locations michigan,florida] [--dedup-threshold 0.95] [--max-probes 100] [--probe-concurrency 3] [--wait] [--format json]';
|
|
2075
|
+
const usage = 'canonry discover run <project> [--icp "..."] [--buyer "..."] [--seed-provider gemini --seed-provider openai] [--icp-angle "..."] [--locations michigan,florida] [--dedup-threshold 0.95] [--max-probes 100] [--probe-concurrency 3] [--wait] [--format json]';
|
|
2074
2076
|
const project = requireProject(input, "discover.run", usage);
|
|
2075
2077
|
await discoverRun(project, {
|
|
2076
2078
|
icp: getString(input.values, "icp"),
|
|
2077
2079
|
buyer: getString(input.values, "buyer"),
|
|
2080
|
+
seedProviders: getStringArray(input.values, "seed-provider"),
|
|
2078
2081
|
icpAngles: getStringArray(input.values, "icp-angle"),
|
|
2079
2082
|
locations: parseLocationsOption(input.values),
|
|
2080
2083
|
dedupThreshold: parseFloatOption(input.values, "dedup-threshold", usage),
|
|
@@ -2095,10 +2098,11 @@ var DISCOVER_CLI_COMMANDS = [
|
|
|
2095
2098
|
},
|
|
2096
2099
|
{
|
|
2097
2100
|
path: ["discover", "seed"],
|
|
2098
|
-
usage: 'canonry discover seed <project> [--icp "..."] [--buyer "..."] [--icp-angle "..."] [--locations michigan,florida] [--dedup-threshold 0.95] [--max-probes 100] [--probe-concurrency 3] [--wait] [--format json]',
|
|
2101
|
+
usage: 'canonry discover seed <project> [--icp "..."] [--buyer "..."] [--seed-provider gemini --seed-provider openai] [--icp-angle "..."] [--locations michigan,florida] [--dedup-threshold 0.95] [--max-probes 100] [--probe-concurrency 3] [--wait] [--format json]',
|
|
2099
2102
|
options: {
|
|
2100
2103
|
icp: stringOption(),
|
|
2101
2104
|
buyer: stringOption(),
|
|
2105
|
+
"seed-provider": multiStringOption(),
|
|
2102
2106
|
"icp-angle": multiStringOption(),
|
|
2103
2107
|
locations: multiStringOption(),
|
|
2104
2108
|
"dedup-threshold": stringOption(),
|
|
@@ -2107,11 +2111,12 @@ var DISCOVER_CLI_COMMANDS = [
|
|
|
2107
2111
|
wait: { type: "boolean", default: false }
|
|
2108
2112
|
},
|
|
2109
2113
|
run: async (input) => {
|
|
2110
|
-
const usage = 'canonry discover seed <project> [--icp "..."] [--buyer "..."] [--icp-angle "..."] [--locations michigan,florida] [--dedup-threshold 0.95] [--max-probes 100] [--probe-concurrency 3] [--wait] [--format json]';
|
|
2114
|
+
const usage = 'canonry discover seed <project> [--icp "..."] [--buyer "..."] [--seed-provider gemini --seed-provider openai] [--icp-angle "..."] [--locations michigan,florida] [--dedup-threshold 0.95] [--max-probes 100] [--probe-concurrency 3] [--wait] [--format json]';
|
|
2111
2115
|
const project = requireProject(input, "discover.seed", usage);
|
|
2112
2116
|
await discoverSeed(project, {
|
|
2113
2117
|
icp: getString(input.values, "icp"),
|
|
2114
2118
|
buyer: getString(input.values, "buyer"),
|
|
2119
|
+
seedProviders: getStringArray(input.values, "seed-provider"),
|
|
2115
2120
|
icpAngles: getStringArray(input.values, "icp-angle"),
|
|
2116
2121
|
locations: parseLocationsOption(input.values),
|
|
2117
2122
|
dedupThreshold: parseFloatOption(input.values, "dedup-threshold", usage),
|
package/dist/index.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import {
|
|
2
2
|
createServer
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-6PHBWPPN.js";
|
|
4
4
|
import {
|
|
5
5
|
loadConfig
|
|
6
|
-
} from "./chunk-
|
|
7
|
-
import "./chunk-
|
|
8
|
-
import "./chunk-
|
|
6
|
+
} from "./chunk-X4LLTNQZ.js";
|
|
7
|
+
import "./chunk-JGRVQJHX.js";
|
|
8
|
+
import "./chunk-YQ4KOC5Z.js";
|
|
9
9
|
export {
|
|
10
10
|
createServer,
|
|
11
11
|
loadConfig
|
package/dist/mcp.js
CHANGED
|
@@ -3,10 +3,10 @@ import {
|
|
|
3
3
|
PACKAGE_VERSION,
|
|
4
4
|
canonryMcpTools,
|
|
5
5
|
createApiClient
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-X4LLTNQZ.js";
|
|
7
7
|
import {
|
|
8
8
|
isReadOnlyKey
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-YQ4KOC5Z.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.
|
|
3
|
+
"version": "4.110.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,8 +65,8 @@
|
|
|
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
68
|
"@ainyc/canonry-config": "0.0.0",
|
|
69
|
+
"@ainyc/canonry-api-client": "0.0.0",
|
|
70
70
|
"@ainyc/canonry-api-routes": "0.0.0",
|
|
71
71
|
"@ainyc/canonry-contracts": "0.0.0",
|
|
72
72
|
"@ainyc/canonry-db": "0.0.0",
|
|
@@ -75,17 +75,17 @@
|
|
|
75
75
|
"@ainyc/canonry-integration-cloud-run": "0.0.0",
|
|
76
76
|
"@ainyc/canonry-integration-commoncrawl": "0.0.0",
|
|
77
77
|
"@ainyc/canonry-integration-google": "0.0.0",
|
|
78
|
-
"@ainyc/canonry-integration-google-places": "0.0.0",
|
|
79
78
|
"@ainyc/canonry-integration-traffic": "0.0.0",
|
|
80
79
|
"@ainyc/canonry-integration-google-business-profile": "0.0.0",
|
|
81
80
|
"@ainyc/canonry-integration-wordpress": "0.0.0",
|
|
82
81
|
"@ainyc/canonry-intelligence": "0.0.0",
|
|
83
|
-
"@ainyc/canonry-
|
|
82
|
+
"@ainyc/canonry-integration-google-places": "0.0.0",
|
|
84
83
|
"@ainyc/canonry-provider-claude": "0.0.0",
|
|
84
|
+
"@ainyc/canonry-provider-cdp": "0.0.0",
|
|
85
85
|
"@ainyc/canonry-provider-gemini": "0.0.0",
|
|
86
|
-
"@ainyc/canonry-provider-perplexity": "0.0.0",
|
|
87
86
|
"@ainyc/canonry-provider-local": "0.0.0",
|
|
88
|
-
"@ainyc/canonry-provider-openai": "0.0.0"
|
|
87
|
+
"@ainyc/canonry-provider-openai": "0.0.0",
|
|
88
|
+
"@ainyc/canonry-provider-perplexity": "0.0.0"
|
|
89
89
|
},
|
|
90
90
|
"scripts": {
|
|
91
91
|
"build": "tsx scripts/copy-agent-assets.ts && tsup && tsx build-web.ts",
|
|
@@ -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-imKX6Edq.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-imKX6Edq.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-B0KSa98X.js";import{A as ee}from"./arrow-left-DDnYTDM6.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};
|