@ainyc/canonry 4.76.2 → 4.77.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-GkTp8zLM.js → BacklinksPage-CwXveumn.js} +1 -1
- package/assets/assets/{ChartPrimitives-0noLg9_T.js → ChartPrimitives-DntKGI5J.js} +1 -1
- package/assets/assets/{ProjectPage-Bg5sKYhO.js → ProjectPage-CVudiU8X.js} +4 -4
- package/assets/assets/{RunRow-BEhaQDol.js → RunRow-DMtYXaxG.js} +1 -1
- package/assets/assets/{RunsPage-CV2wKkaz.js → RunsPage-Cz-YlucO.js} +1 -1
- package/assets/assets/{SettingsPage-Ctij-T9F.js → SettingsPage-BCuG3C-0.js} +1 -1
- package/assets/assets/TrafficPage-DV8X47wa.js +1 -0
- package/assets/assets/TrafficSourceDetailPage-BmYhK9jm.js +1 -0
- package/assets/assets/arrow-left-CUmHyNnF.js +1 -0
- package/assets/assets/extract-error-message-DFjy9_zi.js +1 -0
- package/assets/assets/{index-DRhoqa2-.css → index-BgWgJE7S.css} +1 -1
- package/assets/assets/{index-B-_Dwwhd.js → index-D9smxU6R.js} +4 -4
- package/assets/assets/{trash-2-C65kNIO5.js → trash-2-B_UtEEm8.js} +1 -1
- package/assets/index.html +2 -2
- package/dist/{chunk-BIT3JPMW.js → chunk-BPZWX7YI.js} +95 -15
- package/dist/{chunk-ZUQK4TLL.js → chunk-FB43IMZT.js} +35 -17
- package/dist/{chunk-ETM5EPQH.js → chunk-KPN22EWK.js} +2 -2
- package/dist/{chunk-42TDEKKM.js → chunk-XI6YSTGE.js} +1 -1
- package/dist/cli.js +10 -4
- package/dist/index.js +4 -4
- package/dist/{intelligence-service-QIGX7IVQ.js → intelligence-service-C76ZRMF5.js} +2 -2
- package/dist/mcp.js +2 -2
- package/package.json +8 -8
- package/assets/assets/TrafficPage-TnIPaMcL.js +0 -1
- package/assets/assets/TrafficSourceDetailPage-BBmIwaee.js +0 -1
- package/assets/assets/extract-error-message-BdLppivS.js +0 -1
- package/assets/assets/server-traffic-ScU00kCP.js +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
import{c}from"./index-
|
|
1
|
+
import{c}from"./index-D9smxU6R.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-
|
|
15
|
+
<script type="module" crossorigin src="./assets/index-D9smxU6R.js"></script>
|
|
16
16
|
<link rel="modulepreload" crossorigin href="./assets/vendor-tanstack-Dq7p98wZ.js">
|
|
17
17
|
<link rel="modulepreload" crossorigin href="./assets/vendor-radix-B57xfQbP.js">
|
|
18
18
|
<link rel="modulepreload" crossorigin href="./assets/vendor-recharts-ClRVR6aX.js">
|
|
19
19
|
<link rel="modulepreload" crossorigin href="./assets/vendor-markdown-DK7fbRNb.js">
|
|
20
|
-
<link rel="stylesheet" crossorigin href="./assets/index-
|
|
20
|
+
<link rel="stylesheet" crossorigin href="./assets/index-BgWgJE7S.css">
|
|
21
21
|
</head>
|
|
22
22
|
<body>
|
|
23
23
|
<div id="root"></div>
|
|
@@ -232,7 +232,7 @@ import {
|
|
|
232
232
|
wordpressSchemaDeployResultDtoSchema,
|
|
233
233
|
wordpressSchemaStatusResultDtoSchema,
|
|
234
234
|
wordpressStatusDtoSchema
|
|
235
|
-
} from "./chunk-
|
|
235
|
+
} from "./chunk-KPN22EWK.js";
|
|
236
236
|
|
|
237
237
|
// src/intelligence-service.ts
|
|
238
238
|
import { eq as eq33, desc as desc17, asc as asc4, and as and24, ne as ne5, or as or5, inArray as inArray12, gte as gte6, lte as lte3 } from "drizzle-orm";
|
|
@@ -19153,6 +19153,68 @@ async function getValidToken(store, domain, connectionType, clientId, clientSecr
|
|
|
19153
19153
|
propertyId: conn.propertyId ?? null
|
|
19154
19154
|
};
|
|
19155
19155
|
}
|
|
19156
|
+
function parseGscApiDisabled(message) {
|
|
19157
|
+
if (!/accessNotConfigured|SERVICE_DISABLED|has not been used in project|is disabled/i.test(message)) {
|
|
19158
|
+
return null;
|
|
19159
|
+
}
|
|
19160
|
+
const projectNumber = message.match(/[?&]project=(\d+)/)?.[1] ?? message.match(/project\s+(\d+)/i)?.[1] ?? null;
|
|
19161
|
+
const base = "https://console.developers.google.com/apis/api";
|
|
19162
|
+
const qs = projectNumber ? `?project=${projectNumber}` : "";
|
|
19163
|
+
return {
|
|
19164
|
+
projectNumber,
|
|
19165
|
+
enableUrl: `${base}/searchconsole.googleapis.com/overview${qs}`,
|
|
19166
|
+
indexingApiUrl: `${base}/indexing.googleapis.com/overview${qs}`
|
|
19167
|
+
};
|
|
19168
|
+
}
|
|
19169
|
+
function googleAuthErrorStatus(err) {
|
|
19170
|
+
if (err.statusCode != null) return err.statusCode;
|
|
19171
|
+
const match = err.message.match(/failed \((\d{3})\)/);
|
|
19172
|
+
return match ? Number(match[1]) : null;
|
|
19173
|
+
}
|
|
19174
|
+
function gscErrorToAppError(err, context) {
|
|
19175
|
+
if (err instanceof AppError) return err;
|
|
19176
|
+
if (err instanceof GoogleApiError) {
|
|
19177
|
+
if (err.status === 429) {
|
|
19178
|
+
return quotaExceeded("Google Search Console API (rate limited; retries exhausted)");
|
|
19179
|
+
}
|
|
19180
|
+
if (err.status === 403) {
|
|
19181
|
+
const disabled = parseGscApiDisabled(err.message);
|
|
19182
|
+
if (disabled) {
|
|
19183
|
+
const inProject = disabled.projectNumber ? ` (project ${disabled.projectNumber})` : "";
|
|
19184
|
+
return forbidden(
|
|
19185
|
+
`${context}: the Google Search Console API is not enabled for your Google Cloud project${inProject}. Enable the Search Console API and the Indexing API, wait ~2\u20135 minutes, then retry: ${disabled.enableUrl}`,
|
|
19186
|
+
{ reason: "gsc-api-disabled", upstreamStatus: 403, ...disabled }
|
|
19187
|
+
);
|
|
19188
|
+
}
|
|
19189
|
+
return forbidden(
|
|
19190
|
+
`${context}: the connected Google account does not have access to a verified Search Console property for this domain. Connect the account that owns the property.`,
|
|
19191
|
+
{ reason: "gsc-no-property-access", upstreamStatus: 403 }
|
|
19192
|
+
);
|
|
19193
|
+
}
|
|
19194
|
+
if (err.status === 401) {
|
|
19195
|
+
return forbidden(
|
|
19196
|
+
`${context}: the Google connection has expired or was revoked. Reconnect Google Search Console.`,
|
|
19197
|
+
{ reason: "gsc-reconnect", upstreamStatus: 401 }
|
|
19198
|
+
);
|
|
19199
|
+
}
|
|
19200
|
+
return providerError(`${context}: ${err.message}`, { upstreamStatus: err.status });
|
|
19201
|
+
}
|
|
19202
|
+
if (err instanceof GoogleAuthError) {
|
|
19203
|
+
const status = googleAuthErrorStatus(err);
|
|
19204
|
+
if (status === 429) return quotaExceeded("Google OAuth token refresh (rate limited)");
|
|
19205
|
+
if (status != null && status >= 400 && status < 500) {
|
|
19206
|
+
return forbidden(
|
|
19207
|
+
`${context}: the stored Google credentials are no longer valid (token refresh failed). Reconnect Google Search Console.`,
|
|
19208
|
+
{ reason: "gsc-reconnect", upstreamStatus: status }
|
|
19209
|
+
);
|
|
19210
|
+
}
|
|
19211
|
+
return providerError(
|
|
19212
|
+
`${context}: ${err.message}. Reconnect Google Search Console if this persists.`,
|
|
19213
|
+
status != null ? { upstreamStatus: status } : void 0
|
|
19214
|
+
);
|
|
19215
|
+
}
|
|
19216
|
+
return providerError(`${context}: ${err instanceof Error ? err.message : String(err)}`);
|
|
19217
|
+
}
|
|
19156
19218
|
async function googleRoutes(app, opts) {
|
|
19157
19219
|
if (opts.googleStateSecret === void 0) {
|
|
19158
19220
|
app.log.warn(
|
|
@@ -19375,9 +19437,13 @@ async function googleRoutes(app, opts) {
|
|
|
19375
19437
|
}
|
|
19376
19438
|
const store = requireConnectionStore();
|
|
19377
19439
|
const project = resolveProject(app.db, request.params.name);
|
|
19378
|
-
|
|
19379
|
-
|
|
19380
|
-
|
|
19440
|
+
try {
|
|
19441
|
+
const { accessToken } = await getValidToken(store, project.canonicalDomain, "gsc", googleClientId, googleClientSecret);
|
|
19442
|
+
const sites = await listSites(accessToken);
|
|
19443
|
+
return { sites };
|
|
19444
|
+
} catch (err) {
|
|
19445
|
+
throw gscErrorToAppError(err, "Failed to list Search Console properties");
|
|
19446
|
+
}
|
|
19381
19447
|
});
|
|
19382
19448
|
app.post("/projects/:name/google/gsc/sync", async (request) => {
|
|
19383
19449
|
const store = requireConnectionStore();
|
|
@@ -19470,11 +19536,16 @@ async function googleRoutes(app, opts) {
|
|
|
19470
19536
|
if (!url) {
|
|
19471
19537
|
throw validationError("url is required");
|
|
19472
19538
|
}
|
|
19473
|
-
|
|
19474
|
-
|
|
19475
|
-
|
|
19539
|
+
let result;
|
|
19540
|
+
try {
|
|
19541
|
+
const { accessToken, propertyId } = await getValidToken(store, project.canonicalDomain, "gsc", googleClientId, googleClientSecret);
|
|
19542
|
+
if (!propertyId) {
|
|
19543
|
+
throw validationError("No GSC property configured for this connection");
|
|
19544
|
+
}
|
|
19545
|
+
result = await inspectUrl(accessToken, url, propertyId);
|
|
19546
|
+
} catch (err) {
|
|
19547
|
+
throw gscErrorToAppError(err, "Failed to inspect URL in Search Console");
|
|
19476
19548
|
}
|
|
19477
|
-
const result = await inspectUrl(accessToken, url, propertyId);
|
|
19478
19549
|
const ir = result.inspectionResult;
|
|
19479
19550
|
const idx = ir.indexStatusResult;
|
|
19480
19551
|
const mob = ir.mobileUsabilityResult;
|
|
@@ -19682,12 +19753,16 @@ async function googleRoutes(app, opts) {
|
|
|
19682
19753
|
}
|
|
19683
19754
|
const store = requireConnectionStore();
|
|
19684
19755
|
const project = resolveProject(app.db, request.params.name);
|
|
19685
|
-
|
|
19686
|
-
|
|
19687
|
-
|
|
19756
|
+
try {
|
|
19757
|
+
const { accessToken, propertyId } = await getValidToken(store, project.canonicalDomain, "gsc", googleClientId, googleClientSecret);
|
|
19758
|
+
if (!propertyId) {
|
|
19759
|
+
throw validationError('No GSC property configured for this connection. Set one with "canonry google set-property".');
|
|
19760
|
+
}
|
|
19761
|
+
const sitemaps = await listSitemaps(accessToken, propertyId);
|
|
19762
|
+
return { sitemaps };
|
|
19763
|
+
} catch (err) {
|
|
19764
|
+
throw gscErrorToAppError(err, "Failed to list Search Console sitemaps");
|
|
19688
19765
|
}
|
|
19689
|
-
const sitemaps = await listSitemaps(accessToken, propertyId);
|
|
19690
|
-
return { sitemaps };
|
|
19691
19766
|
});
|
|
19692
19767
|
app.post("/projects/:name/google/gsc/discover-sitemaps", async (request) => {
|
|
19693
19768
|
const { clientId: googleClientId, clientSecret: googleClientSecret } = getAuthConfig();
|
|
@@ -19703,8 +19778,13 @@ async function googleRoutes(app, opts) {
|
|
|
19703
19778
|
if (!conn.propertyId) {
|
|
19704
19779
|
throw validationError("No GSC property configured for this connection");
|
|
19705
19780
|
}
|
|
19706
|
-
|
|
19707
|
-
|
|
19781
|
+
let sitemaps;
|
|
19782
|
+
try {
|
|
19783
|
+
const { accessToken } = await getValidToken(store, project.canonicalDomain, "gsc", googleClientId, googleClientSecret);
|
|
19784
|
+
sitemaps = await listSitemaps(accessToken, conn.propertyId);
|
|
19785
|
+
} catch (err) {
|
|
19786
|
+
throw gscErrorToAppError(err, "Failed to discover Search Console sitemaps");
|
|
19787
|
+
}
|
|
19708
19788
|
if (sitemaps.length === 0) {
|
|
19709
19789
|
throw validationError("No sitemaps found for this GSC property. Submit a sitemap in Google Search Console first.");
|
|
19710
19790
|
}
|
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
loadConfig,
|
|
10
10
|
loadConfigRaw,
|
|
11
11
|
saveConfigPatch
|
|
12
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-XI6YSTGE.js";
|
|
13
13
|
import {
|
|
14
14
|
CC_CACHE_DIR,
|
|
15
15
|
DUCKDB_SPEC,
|
|
@@ -97,7 +97,7 @@ import {
|
|
|
97
97
|
siteAuditPages,
|
|
98
98
|
siteAuditSnapshots,
|
|
99
99
|
usageCounters
|
|
100
|
-
} from "./chunk-
|
|
100
|
+
} from "./chunk-BPZWX7YI.js";
|
|
101
101
|
import {
|
|
102
102
|
AGENT_MEMORY_VALUE_MAX_BYTES,
|
|
103
103
|
AGENT_PROVIDER_IDS,
|
|
@@ -149,7 +149,7 @@ import {
|
|
|
149
149
|
validationError,
|
|
150
150
|
winnabilityClassLabel,
|
|
151
151
|
withRetry
|
|
152
|
-
} from "./chunk-
|
|
152
|
+
} from "./chunk-KPN22EWK.js";
|
|
153
153
|
|
|
154
154
|
// src/telemetry.ts
|
|
155
155
|
import crypto from "crypto";
|
|
@@ -476,15 +476,17 @@ function resolveModel(config) {
|
|
|
476
476
|
return config.model || DEFAULT_MODEL;
|
|
477
477
|
}
|
|
478
478
|
function createClient2(config) {
|
|
479
|
+
const httpOptions = config.baseUrl ? { httpOptions: { baseUrl: config.baseUrl } } : {};
|
|
479
480
|
if (isVertexConfig(config)) {
|
|
480
481
|
return new GoogleGenAI({
|
|
481
482
|
vertexai: true,
|
|
482
483
|
project: config.vertexProject,
|
|
483
484
|
location: config.vertexRegion || "us-central1",
|
|
484
|
-
...config.vertexCredentials ? { googleAuthOptions: { keyFilename: config.vertexCredentials } } : {}
|
|
485
|
+
...config.vertexCredentials ? { googleAuthOptions: { keyFilename: config.vertexCredentials } } : {},
|
|
486
|
+
...httpOptions
|
|
485
487
|
});
|
|
486
488
|
}
|
|
487
|
-
return new GoogleGenAI({ apiKey: config.apiKey });
|
|
489
|
+
return new GoogleGenAI({ apiKey: config.apiKey, ...httpOptions });
|
|
488
490
|
}
|
|
489
491
|
function validateConfig(config) {
|
|
490
492
|
if ("vertexProject" in config && config.vertexProject !== void 0 && config.vertexProject.trim().length === 0) {
|
|
@@ -749,7 +751,7 @@ async function embedQueries(queries2, options) {
|
|
|
749
751
|
if (!options.apiKey && !options.client) {
|
|
750
752
|
throw new Error("embedQueries: missing apiKey");
|
|
751
753
|
}
|
|
752
|
-
const client = options.client ?? createGeminiEmbedClient(options.apiKey);
|
|
754
|
+
const client = options.client ?? createGeminiEmbedClient(options.apiKey, options.baseUrl);
|
|
753
755
|
return client.embedBatch(queries2, {
|
|
754
756
|
model: options.model ?? DEFAULT_EMBED_MODEL,
|
|
755
757
|
taskType: CLUSTERING_TASK_TYPE,
|
|
@@ -770,8 +772,11 @@ function extractEmbeddingVectors(response, expectedLength) {
|
|
|
770
772
|
return e.values;
|
|
771
773
|
});
|
|
772
774
|
}
|
|
773
|
-
function
|
|
774
|
-
|
|
775
|
+
function createEmbedGenAI(apiKey, baseUrl) {
|
|
776
|
+
return new GoogleGenAI2({ apiKey, ...baseUrl ? { httpOptions: { baseUrl } } : {} });
|
|
777
|
+
}
|
|
778
|
+
function createGeminiEmbedClient(apiKey, baseUrl) {
|
|
779
|
+
const genai = createEmbedGenAI(apiKey, baseUrl);
|
|
775
780
|
return {
|
|
776
781
|
async embedBatch(queries2, opts) {
|
|
777
782
|
const response = await genai.models.embedContent({
|
|
@@ -792,6 +797,7 @@ function toGeminiConfig(config) {
|
|
|
792
797
|
return {
|
|
793
798
|
apiKey: config.apiKey ?? "",
|
|
794
799
|
model: config.model,
|
|
800
|
+
baseUrl: config.baseUrl,
|
|
795
801
|
quotaPolicy: config.quotaPolicy,
|
|
796
802
|
vertexProject: config.vertexProject,
|
|
797
803
|
vertexRegion: config.vertexRegion,
|
|
@@ -895,6 +901,12 @@ async function withRetry3(fn, options = {}) {
|
|
|
895
901
|
|
|
896
902
|
// ../provider-openai/src/normalize.ts
|
|
897
903
|
var DEFAULT_MODEL2 = "gpt-5.4";
|
|
904
|
+
function createClient3(config) {
|
|
905
|
+
return new OpenAI({
|
|
906
|
+
apiKey: config.apiKey,
|
|
907
|
+
...config.baseUrl ? { baseURL: config.baseUrl } : {}
|
|
908
|
+
});
|
|
909
|
+
}
|
|
898
910
|
function validateConfig2(config) {
|
|
899
911
|
if (!config.apiKey || config.apiKey.length === 0) {
|
|
900
912
|
return { ok: false, provider: "openai", message: "missing api key" };
|
|
@@ -910,7 +922,7 @@ async function healthcheck2(config) {
|
|
|
910
922
|
const validation = validateConfig2(config);
|
|
911
923
|
if (!validation.ok) return validation;
|
|
912
924
|
try {
|
|
913
|
-
const client =
|
|
925
|
+
const client = createClient3(config);
|
|
914
926
|
const response = await withRetry3(
|
|
915
927
|
() => client.responses.create({
|
|
916
928
|
model: config.model ?? DEFAULT_MODEL2,
|
|
@@ -935,7 +947,7 @@ async function healthcheck2(config) {
|
|
|
935
947
|
}
|
|
936
948
|
async function executeTrackedQuery2(input) {
|
|
937
949
|
const model = input.config.model ?? DEFAULT_MODEL2;
|
|
938
|
-
const client =
|
|
950
|
+
const client = createClient3(input.config);
|
|
939
951
|
const webSearchTool = { type: "web_search" };
|
|
940
952
|
if (input.location) {
|
|
941
953
|
webSearchTool.user_location = {
|
|
@@ -1108,7 +1120,7 @@ function extractDomainFromUri2(uri) {
|
|
|
1108
1120
|
}
|
|
1109
1121
|
async function generateText2(prompt, config) {
|
|
1110
1122
|
const model = config.model ?? DEFAULT_MODEL2;
|
|
1111
|
-
const client =
|
|
1123
|
+
const client = createClient3(config);
|
|
1112
1124
|
const response = await withRetry3(
|
|
1113
1125
|
() => client.responses.create({
|
|
1114
1126
|
model,
|
|
@@ -1130,6 +1142,7 @@ function toOpenAIConfig(config) {
|
|
|
1130
1142
|
return {
|
|
1131
1143
|
apiKey: config.apiKey ?? "",
|
|
1132
1144
|
model: config.model,
|
|
1145
|
+
baseUrl: config.baseUrl,
|
|
1133
1146
|
quotaPolicy: config.quotaPolicy
|
|
1134
1147
|
};
|
|
1135
1148
|
}
|
|
@@ -4933,7 +4946,7 @@ function buildDefaultDeps(registry) {
|
|
|
4933
4946
|
},
|
|
4934
4947
|
async embed(queries2) {
|
|
4935
4948
|
if (cfg.apiKey) {
|
|
4936
|
-
return embedQueries(queries2, { apiKey: cfg.apiKey });
|
|
4949
|
+
return embedQueries(queries2, { apiKey: cfg.apiKey, baseUrl: cfg.baseUrl });
|
|
4937
4950
|
}
|
|
4938
4951
|
throw new Error("Discovery currently requires a Gemini API key. Vertex-mode embeddings are not yet implemented.");
|
|
4939
4952
|
},
|
|
@@ -5761,7 +5774,7 @@ function readStoredGroundingSources(rawResponse) {
|
|
|
5761
5774
|
return result;
|
|
5762
5775
|
}
|
|
5763
5776
|
async function backfillInsightsCommand(project, opts) {
|
|
5764
|
-
const { IntelligenceService: IntelligenceService2 } = await import("./intelligence-service-
|
|
5777
|
+
const { IntelligenceService: IntelligenceService2 } = await import("./intelligence-service-C76ZRMF5.js");
|
|
5765
5778
|
const config = loadConfig();
|
|
5766
5779
|
const db = createClient(config.database);
|
|
5767
5780
|
migrate(db);
|
|
@@ -9452,11 +9465,16 @@ var BROWSER_ADAPTERS = [
|
|
|
9452
9465
|
var adapterMap = Object.fromEntries(
|
|
9453
9466
|
API_ADAPTERS.map((a) => [a.name, a])
|
|
9454
9467
|
);
|
|
9455
|
-
function summarizeProviderConfig(
|
|
9468
|
+
function summarizeProviderConfig(config) {
|
|
9456
9469
|
return {
|
|
9457
9470
|
configured: Boolean(config?.apiKey || config?.baseUrl),
|
|
9458
9471
|
model: config?.model ?? null,
|
|
9459
|
-
baseUrl
|
|
9472
|
+
// baseUrl is surfaced for ALL providers, not just local — gemini/openai now
|
|
9473
|
+
// honor a custom endpoint, so repointing one must show in the settings
|
|
9474
|
+
// summary AND produce an audit diff. Omitting it for API providers would let
|
|
9475
|
+
// an endpoint redirect (a credential-exfiltration vector on a box where the
|
|
9476
|
+
// provider key is the carrier) happen with no audit trail.
|
|
9477
|
+
baseUrl: config?.baseUrl ?? null,
|
|
9460
9478
|
quota: { ...config?.quota ?? DEFAULT_QUOTA }
|
|
9461
9479
|
};
|
|
9462
9480
|
}
|
|
@@ -10330,7 +10348,7 @@ async function createServer(opts) {
|
|
|
10330
10348
|
if (!adapterMap[name]) return null;
|
|
10331
10349
|
if (!opts.config.providers) opts.config.providers = {};
|
|
10332
10350
|
const existing = opts.config.providers[name];
|
|
10333
|
-
const beforeConfig = summarizeProviderConfig(
|
|
10351
|
+
const beforeConfig = summarizeProviderConfig(existing);
|
|
10334
10352
|
const mergedQuota = incomingQuota ? { ...existing?.quota ?? DEFAULT_QUOTA, ...incomingQuota } : existing?.quota;
|
|
10335
10353
|
opts.config.providers[name] = {
|
|
10336
10354
|
apiKey: apiKey || existing?.apiKey,
|
|
@@ -10369,7 +10387,7 @@ async function createServer(opts) {
|
|
|
10369
10387
|
entry.vertexConfigured = !!opts.config.providers?.[name]?.vertexProject;
|
|
10370
10388
|
}
|
|
10371
10389
|
}
|
|
10372
|
-
const afterConfig = summarizeProviderConfig(
|
|
10390
|
+
const afterConfig = summarizeProviderConfig(opts.config.providers[name]);
|
|
10373
10391
|
if (JSON.stringify(beforeConfig) !== JSON.stringify(afterConfig)) {
|
|
10374
10392
|
const diff = JSON.stringify({
|
|
10375
10393
|
before: existing ? beforeConfig : null,
|
|
@@ -41,8 +41,8 @@ function authRequired(message = "Authentication required") {
|
|
|
41
41
|
function authInvalid() {
|
|
42
42
|
return new AppError("AUTH_INVALID", "Invalid API key", 401);
|
|
43
43
|
}
|
|
44
|
-
function forbidden(message = "Forbidden") {
|
|
45
|
-
return new AppError("FORBIDDEN", message, 403);
|
|
44
|
+
function forbidden(message = "Forbidden", details) {
|
|
45
|
+
return new AppError("FORBIDDEN", message, 403, details);
|
|
46
46
|
}
|
|
47
47
|
function quotaExceeded(metric) {
|
|
48
48
|
return new AppError("QUOTA_EXCEEDED", `Quota exceeded for ${metric}`, 429);
|
package/dist/cli.js
CHANGED
|
@@ -27,7 +27,7 @@ import {
|
|
|
27
27
|
setTelemetrySource,
|
|
28
28
|
showFirstRunNotice,
|
|
29
29
|
trackEvent
|
|
30
|
-
} from "./chunk-
|
|
30
|
+
} from "./chunk-FB43IMZT.js";
|
|
31
31
|
import {
|
|
32
32
|
CliError,
|
|
33
33
|
EXIT_SYSTEM_ERROR,
|
|
@@ -44,7 +44,7 @@ import {
|
|
|
44
44
|
saveConfig,
|
|
45
45
|
saveConfigPatch,
|
|
46
46
|
usageError
|
|
47
|
-
} from "./chunk-
|
|
47
|
+
} from "./chunk-XI6YSTGE.js";
|
|
48
48
|
import {
|
|
49
49
|
apiKeys,
|
|
50
50
|
createClient,
|
|
@@ -52,7 +52,7 @@ import {
|
|
|
52
52
|
projects,
|
|
53
53
|
queries,
|
|
54
54
|
renderReportHtml
|
|
55
|
-
} from "./chunk-
|
|
55
|
+
} from "./chunk-BPZWX7YI.js";
|
|
56
56
|
import {
|
|
57
57
|
CcReleaseSyncStatuses,
|
|
58
58
|
CheckScopes,
|
|
@@ -72,7 +72,7 @@ import {
|
|
|
72
72
|
providerQuotaPolicySchema,
|
|
73
73
|
resolveProviderInput,
|
|
74
74
|
winnabilityClassSchema
|
|
75
|
-
} from "./chunk-
|
|
75
|
+
} from "./chunk-KPN22EWK.js";
|
|
76
76
|
|
|
77
77
|
// src/cli.ts
|
|
78
78
|
import { pathToFileURL } from "url";
|
|
@@ -9633,6 +9633,7 @@ var envSchema = z.object({
|
|
|
9633
9633
|
// Gemini
|
|
9634
9634
|
GEMINI_API_KEY: z.string().optional(),
|
|
9635
9635
|
GEMINI_MODEL: z.string().optional(),
|
|
9636
|
+
GEMINI_BASE_URL: z.string().optional(),
|
|
9636
9637
|
GEMINI_MAX_CONCURRENCY: z.coerce.number().int().positive().default(2),
|
|
9637
9638
|
GEMINI_MAX_REQUESTS_PER_MINUTE: z.coerce.number().int().positive().default(10),
|
|
9638
9639
|
GEMINI_MAX_REQUESTS_PER_DAY: z.coerce.number().int().positive().default(1e3),
|
|
@@ -9643,6 +9644,7 @@ var envSchema = z.object({
|
|
|
9643
9644
|
// OpenAI
|
|
9644
9645
|
OPENAI_API_KEY: z.string().optional(),
|
|
9645
9646
|
OPENAI_MODEL: z.string().optional(),
|
|
9647
|
+
OPENAI_BASE_URL: z.string().optional(),
|
|
9646
9648
|
OPENAI_MAX_CONCURRENCY: z.coerce.number().int().positive().default(2),
|
|
9647
9649
|
OPENAI_MAX_REQUESTS_PER_MINUTE: z.coerce.number().int().positive().default(10),
|
|
9648
9650
|
OPENAI_MAX_REQUESTS_PER_DAY: z.coerce.number().int().positive().default(1e3),
|
|
@@ -9669,11 +9671,13 @@ var bootstrapEnvSchema = z.object({
|
|
|
9669
9671
|
CANONRY_DATABASE_PATH: z.string().optional(),
|
|
9670
9672
|
GEMINI_API_KEY: z.string().optional(),
|
|
9671
9673
|
GEMINI_MODEL: z.string().optional(),
|
|
9674
|
+
GEMINI_BASE_URL: z.string().optional(),
|
|
9672
9675
|
GEMINI_VERTEX_PROJECT: z.string().optional(),
|
|
9673
9676
|
GEMINI_VERTEX_REGION: z.string().optional(),
|
|
9674
9677
|
GEMINI_VERTEX_CREDENTIALS: z.string().optional(),
|
|
9675
9678
|
OPENAI_API_KEY: z.string().optional(),
|
|
9676
9679
|
OPENAI_MODEL: z.string().optional(),
|
|
9680
|
+
OPENAI_BASE_URL: z.string().optional(),
|
|
9677
9681
|
ANTHROPIC_API_KEY: z.string().optional(),
|
|
9678
9682
|
ANTHROPIC_MODEL: z.string().optional(),
|
|
9679
9683
|
PERPLEXITY_API_KEY: z.string().optional(),
|
|
@@ -9692,6 +9696,7 @@ function getBootstrapEnv(source, overrides) {
|
|
|
9692
9696
|
providers.gemini = {
|
|
9693
9697
|
apiKey: parsed.GEMINI_API_KEY ?? "",
|
|
9694
9698
|
model: parsed.GEMINI_MODEL || "gemini-2.5-flash",
|
|
9699
|
+
baseUrl: parsed.GEMINI_BASE_URL,
|
|
9695
9700
|
quota: providerQuotaPolicySchema.parse({
|
|
9696
9701
|
maxConcurrency: 2,
|
|
9697
9702
|
maxRequestsPerMinute: 10,
|
|
@@ -9706,6 +9711,7 @@ function getBootstrapEnv(source, overrides) {
|
|
|
9706
9711
|
providers.openai = {
|
|
9707
9712
|
apiKey: parsed.OPENAI_API_KEY,
|
|
9708
9713
|
model: parsed.OPENAI_MODEL || "gpt-5.4",
|
|
9714
|
+
baseUrl: parsed.OPENAI_BASE_URL,
|
|
9709
9715
|
quota: providerQuotaPolicySchema.parse({
|
|
9710
9716
|
maxConcurrency: 2,
|
|
9711
9717
|
maxRequestsPerMinute: 10,
|
package/dist/index.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import {
|
|
2
2
|
createServer
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-FB43IMZT.js";
|
|
4
4
|
import {
|
|
5
5
|
loadConfig
|
|
6
|
-
} from "./chunk-
|
|
7
|
-
import "./chunk-
|
|
8
|
-
import "./chunk-
|
|
6
|
+
} from "./chunk-XI6YSTGE.js";
|
|
7
|
+
import "./chunk-BPZWX7YI.js";
|
|
8
|
+
import "./chunk-KPN22EWK.js";
|
|
9
9
|
export {
|
|
10
10
|
createServer,
|
|
11
11
|
loadConfig
|
package/dist/mcp.js
CHANGED
|
@@ -3,8 +3,8 @@ import {
|
|
|
3
3
|
PACKAGE_VERSION,
|
|
4
4
|
canonryMcpTools,
|
|
5
5
|
createApiClient
|
|
6
|
-
} from "./chunk-
|
|
7
|
-
import "./chunk-
|
|
6
|
+
} from "./chunk-XI6YSTGE.js";
|
|
7
|
+
import "./chunk-KPN22EWK.js";
|
|
8
8
|
|
|
9
9
|
// src/mcp/cli.ts
|
|
10
10
|
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.77.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",
|
|
@@ -64,24 +64,24 @@
|
|
|
64
64
|
"tsx": "^4.19.0",
|
|
65
65
|
"@ainyc/canonry-api-client": "0.0.0",
|
|
66
66
|
"@ainyc/canonry-api-routes": "0.0.0",
|
|
67
|
+
"@ainyc/canonry-config": "0.0.0",
|
|
67
68
|
"@ainyc/canonry-contracts": "0.0.0",
|
|
68
69
|
"@ainyc/canonry-db": "0.0.0",
|
|
69
70
|
"@ainyc/canonry-integration-bing": "0.0.0",
|
|
70
|
-
"@ainyc/canonry-config": "0.0.0",
|
|
71
71
|
"@ainyc/canonry-integration-cloud-run": "0.0.0",
|
|
72
72
|
"@ainyc/canonry-integration-commoncrawl": "0.0.0",
|
|
73
73
|
"@ainyc/canonry-integration-google": "0.0.0",
|
|
74
|
-
"@ainyc/canonry-integration-google-places": "0.0.0",
|
|
75
74
|
"@ainyc/canonry-integration-traffic": "0.0.0",
|
|
75
|
+
"@ainyc/canonry-integration-google-places": "0.0.0",
|
|
76
76
|
"@ainyc/canonry-integration-google-business-profile": "0.0.0",
|
|
77
|
-
"@ainyc/canonry-provider-cdp": "0.0.0",
|
|
78
|
-
"@ainyc/canonry-intelligence": "0.0.0",
|
|
79
77
|
"@ainyc/canonry-integration-wordpress": "0.0.0",
|
|
78
|
+
"@ainyc/canonry-intelligence": "0.0.0",
|
|
79
|
+
"@ainyc/canonry-provider-cdp": "0.0.0",
|
|
80
|
+
"@ainyc/canonry-provider-local": "0.0.0",
|
|
80
81
|
"@ainyc/canonry-provider-gemini": "0.0.0",
|
|
81
|
-
"@ainyc/canonry-provider-claude": "0.0.0",
|
|
82
82
|
"@ainyc/canonry-provider-openai": "0.0.0",
|
|
83
|
-
"@ainyc/canonry-provider-
|
|
84
|
-
"@ainyc/canonry-provider-
|
|
83
|
+
"@ainyc/canonry-provider-claude": "0.0.0",
|
|
84
|
+
"@ainyc/canonry-provider-perplexity": "0.0.0"
|
|
85
85
|
},
|
|
86
86
|
"scripts": {
|
|
87
87
|
"build": "tsx scripts/copy-agent-assets.ts && tsup && tsx build-web.ts",
|
|
@@ -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-Dq7p98wZ.js";import{c as T,bS as E,bT as $,bU as I,bV as q,bW as A,bX as B,B as v,i as O,bY as W,b4 as _,l as F,bZ as k,g as C,b_ as G,T as H,b$ as M}from"./index-B-_Dwwhd.js";import{a as J,b as Q,c as Z,d as K,t as Y,R as X}from"./server-traffic-ScU00kCP.js";import{e as P,A as ee}from"./extract-error-message-BdLppivS.js";import"./vendor-radix-B57xfQbP.js";import"./vendor-recharts-ClRVR6aX.js";import"./vendor-markdown-DK7fbRNb.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 W(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(v,{type:"button",variant:"ghost",size:"sm",onClick:n,children:"Close"}),e.jsx(v,{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,j]=l.useState(""),f=J(t||null),{error:g,runConnect:b}=w(t,s),y=()=>b({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:m=>n(m.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:m=>i(m.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: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:"Display name (optional)",description:"Friendly label shown in the dashboard. Defaults to the WordPress host.",children:e.jsx("input",{type:"text",value:c,onChange:m=>j(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 ue({projectName:t,onBack:a,onClose:s}){const[r,n]=l.useState(""),[o,i]=l.useState(""),[d,u]=l.useState(""),[c,j]=l.useState(""),[f,g]=l.useState(""),b=Z(t||null),{error:y,runConnect:m}=w(t,s),z=()=>m({validate:()=>r.trim()?f.trim()?null:"Service-account JSON content is required.":"GCP project ID is required.",mutate:()=>b.mutateAsync({gcpProjectId:r.trim(),serviceName:o.trim()||void 0,location:d.trim()||void 0,displayName:c.trim()||void 0,keyJson:f.trim()}),onConnected:()=>g("")}),h=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:z,isPending:b.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=>j(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=>{h(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,j]=l.useState("production"),[f,g]=l.useState(""),b=Q(t||null),{error:y,runConnect:m}=w(t,s),z=()=>m({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:()=>b.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:z,isPending:b.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:h=>n(h.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:h=>i(h.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: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:"Environment",description:"Which deployment environment's request logs to pull.",children:e.jsxs("select",{value:c,onChange:h=>j(h.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:h=>g(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 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 N(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(_({client:F})).data??[],i=l.useMemo(()=>t||(o[0]?.name??""),[t,o]),d=K(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(v,{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(C,{className:"p-6 text-center text-sm text-zinc-500",children:"Loading sources…"}):u.length===0?e.jsxs(C,{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(v,{type:"button",variant:"outline",size:"sm",onClick:()=>r(!0),children:[e.jsx(k,{className:"size-3.5"}),"Connect a source"]})})]}):e.jsx(me,{projectName:i,sources:u}):e.jsx(C,{className:"p-6 text-center text-sm text-zinc-500",children:"No projects yet."})]}),e.jsx(oe,{open:s,onOpenChange:r,projectName:i})]})}function me({projectName:t,sources:a}){const s=L({queries:a.map(n=>({...G({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 crawler"}),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(H,{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",children:i?"—":N(o?.totals24h.crawlerHits??0)}),e.jsx("td",{className:"px-4 py-3 text-right tabular-nums text-zinc-100",children:i?"—":N(o?.totals24h.aiUserFetchHits??0)}),e.jsx("td",{className:"px-4 py-3 text-right tabular-nums text-zinc-100",children:i?"—":N(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};
|