@canonry/canonry 4.136.1 → 4.137.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -1
- package/assets/assets/{AuditHistoryPanel-B73VrWNb.js → AuditHistoryPanel-B6GEmlVM.js} +1 -1
- package/assets/assets/{BacklinksPage-CLVMg_qx.js → BacklinksPage-Cr3QegNo.js} +1 -1
- package/assets/assets/{ChartPrimitives-P81CbvWc.js → ChartPrimitives-rhsGzU1A.js} +1 -1
- package/assets/assets/{HistoryPage-IcDqWBYW.js → HistoryPage-B3HrxyPP.js} +1 -1
- package/assets/assets/ProjectPage-D3WiL3RI.js +8 -0
- package/assets/assets/{RunRow-BTH3-Tml.js → RunRow-MsV1RLPm.js} +1 -1
- package/assets/assets/{RunsPage-B0k5wjFN.js → RunsPage-BVlv0j3Y.js} +1 -1
- package/assets/assets/{SettingsPage-DGnT3Vdr.js → SettingsPage-Dfd-T4o9.js} +1 -1
- package/assets/assets/{TrafficPage-D79PC0bB.js → TrafficPage-Bl1jj73m.js} +1 -1
- package/assets/assets/{TrafficSourceDetailPage-BYEVjfFm.js → TrafficSourceDetailPage-CinQcKmY.js} +1 -1
- package/assets/assets/{arrow-left-DCdFYJK-.js → arrow-left-CHU6s_Xn.js} +1 -1
- package/assets/assets/extract-error-message-BDdyZgyf.js +1 -0
- package/assets/assets/index-BNulljlk.js +213 -0
- package/assets/assets/{trash-2-CEz2iXBv.js → trash-2-BE5oH1Tj.js} +1 -1
- package/assets/index.html +1 -1
- package/dist/{chunk-PIMWEIJ3.js → chunk-7RF5EDUO.js} +3141 -400
- package/dist/{chunk-YQ2RXMU2.js → chunk-PBTB3FCN.js} +207 -195
- package/dist/{chunk-OESGFFUO.js → chunk-UVWI6VPP.js} +1 -1
- package/dist/{chunk-YMF35LN5.js → chunk-XEX5CLQW.js} +163 -133
- package/dist/cli.js +147 -33
- package/dist/index.d.ts +2 -0
- package/dist/index.js +4 -4
- package/dist/{intelligence-service-BKLR57RQ.js → intelligence-service-JEFHUGVX.js} +2 -2
- package/dist/mcp.js +2 -2
- package/package.json +9 -9
- package/assets/assets/ProjectPage-DTivvVIl.js +0 -8
- package/assets/assets/extract-error-message-bM0aUBUI.js +0 -1
- package/assets/assets/index-CMuA-uEN.js +0 -210
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
loadConfig,
|
|
11
11
|
loadConfigRaw,
|
|
12
12
|
saveConfigPatch
|
|
13
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-UVWI6VPP.js";
|
|
14
14
|
import {
|
|
15
15
|
CC_CACHE_DIR,
|
|
16
16
|
DUCKDB_SPEC,
|
|
@@ -119,7 +119,7 @@ import {
|
|
|
119
119
|
siteAuditPages,
|
|
120
120
|
siteAuditSnapshots,
|
|
121
121
|
usageCounters
|
|
122
|
-
} from "./chunk-
|
|
122
|
+
} from "./chunk-PBTB3FCN.js";
|
|
123
123
|
import {
|
|
124
124
|
AGENT_MEMORY_VALUE_MAX_BYTES,
|
|
125
125
|
AGENT_PROVIDER_IDS,
|
|
@@ -135,6 +135,7 @@ import {
|
|
|
135
135
|
LLM_CAPABILITIES,
|
|
136
136
|
LlmCapabilities,
|
|
137
137
|
MemorySources,
|
|
138
|
+
ONBOARDING_FLOW_VERSION,
|
|
138
139
|
ProviderNames,
|
|
139
140
|
ResearchQueryStatuses,
|
|
140
141
|
ResearchRunStatuses,
|
|
@@ -159,6 +160,7 @@ import {
|
|
|
159
160
|
agentMemoryUpsertRequestSchema,
|
|
160
161
|
authInvalid,
|
|
161
162
|
authRequired,
|
|
163
|
+
bucketOnboardingCount,
|
|
162
164
|
buildRunErrorFromMessages,
|
|
163
165
|
classifySkillFile,
|
|
164
166
|
coerceSkillManifest,
|
|
@@ -173,6 +175,8 @@ import {
|
|
|
173
175
|
factorStatusFromScore,
|
|
174
176
|
formatIsoDateInTimeZone,
|
|
175
177
|
frameAncestorsHeaderValue,
|
|
178
|
+
hostMatchesAnyDomain,
|
|
179
|
+
hostMatchesDomain,
|
|
176
180
|
hostOf,
|
|
177
181
|
isAgentProviderId,
|
|
178
182
|
isBrowserProvider,
|
|
@@ -185,16 +189,19 @@ import {
|
|
|
185
189
|
notFound,
|
|
186
190
|
parseOriginList,
|
|
187
191
|
providerError,
|
|
192
|
+
registrableDomain,
|
|
188
193
|
resolveSnapshotRequestQueries,
|
|
189
194
|
serializeForInlineScript,
|
|
190
195
|
serializeRunError,
|
|
191
196
|
skillsClientSchema,
|
|
192
197
|
splitList,
|
|
193
198
|
startOfDayHourInTimeZone,
|
|
199
|
+
textContainsBrandAlias,
|
|
200
|
+
textContainsDomain,
|
|
194
201
|
validationError,
|
|
195
202
|
winnabilityClassLabel,
|
|
196
203
|
withRetry
|
|
197
|
-
} from "./chunk-
|
|
204
|
+
} from "./chunk-7RF5EDUO.js";
|
|
198
205
|
|
|
199
206
|
// src/telemetry.ts
|
|
200
207
|
import crypto from "crypto";
|
|
@@ -209,8 +216,42 @@ var ANON_ID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]
|
|
|
209
216
|
function shouldDropTelemetryEvent(event, properties) {
|
|
210
217
|
return isGhostTelemetryEvent(event, properties);
|
|
211
218
|
}
|
|
219
|
+
function bucketCliCommandDuration(durationMs) {
|
|
220
|
+
const duration = Number.isFinite(durationMs) ? Math.max(0, durationMs) : 0;
|
|
221
|
+
if (duration < 1e3) return "under_1s";
|
|
222
|
+
if (duration < 1e4) return "1s_to_10s";
|
|
223
|
+
if (duration < 6e4) return "10s_to_1m";
|
|
224
|
+
if (duration < 3e5) return "1m_to_5m";
|
|
225
|
+
if (duration < 18e5) return "5m_to_30m";
|
|
226
|
+
return "30m_or_more";
|
|
227
|
+
}
|
|
228
|
+
function trackCliCommandFinished(input) {
|
|
229
|
+
trackEvent(
|
|
230
|
+
"cli.command.finished",
|
|
231
|
+
{
|
|
232
|
+
command: input.command,
|
|
233
|
+
success: input.success,
|
|
234
|
+
duration_bucket: bucketCliCommandDuration(input.durationMs),
|
|
235
|
+
...input.setupState ? { setup_state: input.setupState } : {}
|
|
236
|
+
},
|
|
237
|
+
input.errorCode ? { errorCode: input.errorCode } : void 0
|
|
238
|
+
);
|
|
239
|
+
}
|
|
212
240
|
var SESSION_ID = crypto.randomUUID();
|
|
213
241
|
var CURRENT_SOURCE = "cli";
|
|
242
|
+
var TELEMETRY_SOURCE_VALUES = /* @__PURE__ */ new Set([
|
|
243
|
+
"cli",
|
|
244
|
+
"cli-server",
|
|
245
|
+
"api",
|
|
246
|
+
"mcp-server",
|
|
247
|
+
"wp-plugin",
|
|
248
|
+
"dashboard",
|
|
249
|
+
"agent-runtime"
|
|
250
|
+
]);
|
|
251
|
+
function envSourceOverride() {
|
|
252
|
+
const raw = process.env.CANONRY_TELEMETRY_SOURCE?.trim();
|
|
253
|
+
return raw && TELEMETRY_SOURCE_VALUES.has(raw) ? raw : void 0;
|
|
254
|
+
}
|
|
214
255
|
function setTelemetrySource(source) {
|
|
215
256
|
CURRENT_SOURCE = source;
|
|
216
257
|
}
|
|
@@ -326,9 +367,10 @@ function trackEvent(event, properties, options) {
|
|
|
326
367
|
const anonymousId = getOrCreateAnonymousId();
|
|
327
368
|
if (!anonymousId) return;
|
|
328
369
|
const payload = {
|
|
370
|
+
eventId: options?.eventId ?? crypto.randomUUID(),
|
|
329
371
|
anonymousId,
|
|
330
372
|
sessionId: SESSION_ID,
|
|
331
|
-
source: options?.source ?? CURRENT_SOURCE,
|
|
373
|
+
source: options?.source ?? envSourceOverride() ?? CURRENT_SOURCE,
|
|
332
374
|
event,
|
|
333
375
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
334
376
|
version: VERSION,
|
|
@@ -342,13 +384,17 @@ function trackEvent(event, properties, options) {
|
|
|
342
384
|
const controller = new AbortController();
|
|
343
385
|
const timeout = setTimeout(() => controller.abort(), TIMEOUT_MS);
|
|
344
386
|
timeout.unref();
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
387
|
+
try {
|
|
388
|
+
void fetch(TELEMETRY_ENDPOINT, {
|
|
389
|
+
method: "POST",
|
|
390
|
+
headers: { "Content-Type": "application/json" },
|
|
391
|
+
body: JSON.stringify(payload),
|
|
392
|
+
signal: controller.signal
|
|
393
|
+
}).catch(() => {
|
|
394
|
+
}).finally(() => clearTimeout(timeout));
|
|
395
|
+
} catch {
|
|
396
|
+
clearTimeout(timeout);
|
|
397
|
+
}
|
|
352
398
|
}
|
|
353
399
|
|
|
354
400
|
// src/update-check.ts
|
|
@@ -728,17 +774,15 @@ function extractCitedDomainsFromSources(groundingSources) {
|
|
|
728
774
|
return [...domains];
|
|
729
775
|
}
|
|
730
776
|
function extractDomainFromTitle(title) {
|
|
731
|
-
const trimmed = title.trim()
|
|
732
|
-
|
|
733
|
-
return trimmed.replace(/^www\./, "");
|
|
734
|
-
}
|
|
735
|
-
return null;
|
|
777
|
+
const trimmed = title.trim();
|
|
778
|
+
return registrableDomain(trimmed) ? hostOf(trimmed) : null;
|
|
736
779
|
}
|
|
737
780
|
function extractDomainFromUri(uri) {
|
|
738
781
|
try {
|
|
739
782
|
const url = new URL(uri);
|
|
740
|
-
const hostname =
|
|
741
|
-
if (
|
|
783
|
+
const hostname = hostOf(uri);
|
|
784
|
+
if (!hostname || !registrableDomain(hostname)) return null;
|
|
785
|
+
if (hostMatchesAnyDomain(hostname, AI_ENGINE_SELF_DOMAINS.chatgpt)) {
|
|
742
786
|
return null;
|
|
743
787
|
}
|
|
744
788
|
if (hostname === VERTEX_AI_SEARCH_PROXY_DOMAIN) {
|
|
@@ -747,8 +791,7 @@ function extractDomainFromUri(uri) {
|
|
|
747
791
|
try {
|
|
748
792
|
const decoded = decodeURIComponent(redirectPath);
|
|
749
793
|
if (decoded.startsWith("http")) {
|
|
750
|
-
|
|
751
|
-
return realUrl.hostname.replace(/^www\./, "");
|
|
794
|
+
return hostOf(decoded);
|
|
752
795
|
}
|
|
753
796
|
} catch {
|
|
754
797
|
}
|
|
@@ -1169,16 +1212,9 @@ function extractCitedDomainsFromSources2(groundingSources) {
|
|
|
1169
1212
|
return [...domains];
|
|
1170
1213
|
}
|
|
1171
1214
|
function extractDomainFromUri2(uri) {
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
if (AI_ENGINE_SELF_DOMAINS.chatgpt.some((self) => hostname.includes(self))) {
|
|
1176
|
-
return null;
|
|
1177
|
-
}
|
|
1178
|
-
return hostname;
|
|
1179
|
-
} catch {
|
|
1180
|
-
return null;
|
|
1181
|
-
}
|
|
1215
|
+
const hostname = hostOf(uri);
|
|
1216
|
+
if (!hostname || !registrableDomain(hostname) || hostMatchesAnyDomain(hostname, AI_ENGINE_SELF_DOMAINS.chatgpt)) return null;
|
|
1217
|
+
return hostname;
|
|
1182
1218
|
}
|
|
1183
1219
|
async function generateText2(prompt, config) {
|
|
1184
1220
|
const model = config.model ?? DEFAULT_MODEL2;
|
|
@@ -1538,16 +1574,11 @@ function extractCitedDomainsFromSources3(groundingSources) {
|
|
|
1538
1574
|
return [...domains];
|
|
1539
1575
|
}
|
|
1540
1576
|
function extractDomainFromUri3(uri) {
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
const hostname = url.hostname.replace(/^www\./, "").toLowerCase();
|
|
1544
|
-
if (AI_ENGINE_SELF_DOMAINS.chatgpt.some((self) => hostname.includes(self))) {
|
|
1545
|
-
return null;
|
|
1546
|
-
}
|
|
1547
|
-
return hostname;
|
|
1548
|
-
} catch {
|
|
1577
|
+
const hostname = hostOf(uri);
|
|
1578
|
+
if (!hostname || !registrableDomain(hostname) || hostMatchesAnyDomain(hostname, AI_ENGINE_SELF_DOMAINS.chatgpt)) {
|
|
1549
1579
|
return null;
|
|
1550
1580
|
}
|
|
1581
|
+
return hostname;
|
|
1551
1582
|
}
|
|
1552
1583
|
async function generateText3(prompt, config) {
|
|
1553
1584
|
const model = resolveModel2(config);
|
|
@@ -1755,11 +1786,10 @@ async function executeTrackedQuery4(input) {
|
|
|
1755
1786
|
}
|
|
1756
1787
|
function normalizeResult4(raw) {
|
|
1757
1788
|
const answerText = extractAnswerText2(raw.rawResponse);
|
|
1758
|
-
const
|
|
1759
|
-
const
|
|
1760
|
-
uri
|
|
1761
|
-
|
|
1762
|
-
}));
|
|
1789
|
+
const groundingSources = raw.groundingSources;
|
|
1790
|
+
const citedDomains = [...new Set(
|
|
1791
|
+
groundingSources.map((source) => hostOf(source.uri)).filter((domain) => domain !== null && registrableDomain(domain).length > 0)
|
|
1792
|
+
)];
|
|
1763
1793
|
return {
|
|
1764
1794
|
provider: "local",
|
|
1765
1795
|
answerText,
|
|
@@ -1795,19 +1825,6 @@ async function generateText4(prompt, config) {
|
|
|
1795
1825
|
);
|
|
1796
1826
|
return response.choices[0]?.message?.content ?? "";
|
|
1797
1827
|
}
|
|
1798
|
-
function extractDomainMentions(text) {
|
|
1799
|
-
const domains = /* @__PURE__ */ new Set();
|
|
1800
|
-
const urlPattern = /https?:\/\/([a-zA-Z0-9][-a-zA-Z0-9]*(?:\.[a-zA-Z0-9][-a-zA-Z0-9]*)+)/g;
|
|
1801
|
-
let match;
|
|
1802
|
-
while ((match = urlPattern.exec(text)) !== null) {
|
|
1803
|
-
domains.add(match[1].replace(/^www\./, "").toLowerCase());
|
|
1804
|
-
}
|
|
1805
|
-
const domainPattern = /(?:^|[\s(["'])((?:[a-zA-Z0-9][-a-zA-Z0-9]*\.)+(?:com|org|net|io|co|dev|ai|app|edu|gov|biz|info|tech|health|dental|legal|law|med|uk|us|ca|au|de|fr|es|it|nl|se|no|dk|fi|jp|cn|kr|br|mx|ru|in|sg|nz|za)(?:\.[a-zA-Z]{2})?)(?:[\s).,;/"']|$)/g;
|
|
1806
|
-
while ((match = domainPattern.exec(text)) !== null) {
|
|
1807
|
-
domains.add(match[1].replace(/^www\./, "").toLowerCase());
|
|
1808
|
-
}
|
|
1809
|
-
return [...domains];
|
|
1810
|
-
}
|
|
1811
1828
|
function extractServedModel4(rawResponse) {
|
|
1812
1829
|
return normalizeServedModel(rawResponse.model);
|
|
1813
1830
|
}
|
|
@@ -2186,6 +2203,9 @@ var chatgptTarget = {
|
|
|
2186
2203
|
const { result } = await client.Runtime.evaluate({
|
|
2187
2204
|
expression: `(() => {
|
|
2188
2205
|
const SELF_DOMAINS = ${selfDomainsLiteral};
|
|
2206
|
+
const isSelfDomain = hostname => SELF_DOMAINS.some(
|
|
2207
|
+
self => hostname === self || hostname.endsWith('.' + self)
|
|
2208
|
+
);
|
|
2189
2209
|
const sources = [];
|
|
2190
2210
|
const seen = new Set();
|
|
2191
2211
|
const turns = document.querySelectorAll('[data-message-author-role="assistant"]');
|
|
@@ -2209,7 +2229,7 @@ var chatgptTarget = {
|
|
|
2209
2229
|
}
|
|
2210
2230
|
}
|
|
2211
2231
|
|
|
2212
|
-
if (!seen.has(href) && !
|
|
2232
|
+
if (!seen.has(href) && !isSelfDomain(hostname)) {
|
|
2213
2233
|
seen.add(href);
|
|
2214
2234
|
sources.push({
|
|
2215
2235
|
uri: href,
|
|
@@ -2226,7 +2246,7 @@ var chatgptTarget = {
|
|
|
2226
2246
|
if (href && !seen.has(href)) {
|
|
2227
2247
|
let hostname = '';
|
|
2228
2248
|
try { hostname = new URL(href).hostname.replace(/^www\\./, ''); } catch {}
|
|
2229
|
-
if (!
|
|
2249
|
+
if (!isSelfDomain(hostname)) {
|
|
2230
2250
|
seen.add(href);
|
|
2231
2251
|
sources.push({ uri: href, title: title || hostname || href });
|
|
2232
2252
|
}
|
|
@@ -2313,13 +2333,10 @@ async function captureElementScreenshot(client, selector, outputPath) {
|
|
|
2313
2333
|
function extractCitedDomains(groundingSources) {
|
|
2314
2334
|
const domains = /* @__PURE__ */ new Set();
|
|
2315
2335
|
for (const source of groundingSources) {
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
|
|
2319
|
-
|
|
2320
|
-
domains.add(domain);
|
|
2321
|
-
}
|
|
2322
|
-
} catch {
|
|
2336
|
+
const domain = hostOf(source.uri);
|
|
2337
|
+
if (domain && registrableDomain(domain)) {
|
|
2338
|
+
if (!hostMatchesAnyDomain(domain, AI_ENGINE_SELF_DOMAINS.chatgpt)) domains.add(domain);
|
|
2339
|
+
} else {
|
|
2323
2340
|
const titleDomain = extractDomainFromTitle2(source.title);
|
|
2324
2341
|
if (titleDomain) domains.add(titleDomain);
|
|
2325
2342
|
}
|
|
@@ -2327,12 +2344,9 @@ function extractCitedDomains(groundingSources) {
|
|
|
2327
2344
|
return [...domains];
|
|
2328
2345
|
}
|
|
2329
2346
|
function extractDomainFromTitle2(title) {
|
|
2330
|
-
const domainPattern = /^(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z]{2,}$/i;
|
|
2331
2347
|
const firstWord = title.split(/[\s\-–—|]/)[0]?.trim();
|
|
2332
|
-
if (firstWord
|
|
2333
|
-
|
|
2334
|
-
}
|
|
2335
|
-
return void 0;
|
|
2348
|
+
if (!firstWord || !registrableDomain(firstWord)) return void 0;
|
|
2349
|
+
return hostOf(firstWord) ?? void 0;
|
|
2336
2350
|
}
|
|
2337
2351
|
function normalizeResult5(raw) {
|
|
2338
2352
|
const answerText = typeof raw.rawResponse.answerText === "string" ? raw.rawResponse.answerText : "";
|
|
@@ -2685,16 +2699,9 @@ function extractCitedDomains2(groundingSources) {
|
|
|
2685
2699
|
return [...domains];
|
|
2686
2700
|
}
|
|
2687
2701
|
function extractDomainFromUri4(uri) {
|
|
2688
|
-
|
|
2689
|
-
|
|
2690
|
-
|
|
2691
|
-
if (AI_ENGINE_SELF_DOMAINS.chatgpt.some((self) => hostname.includes(self))) {
|
|
2692
|
-
return null;
|
|
2693
|
-
}
|
|
2694
|
-
return hostname;
|
|
2695
|
-
} catch {
|
|
2696
|
-
return null;
|
|
2697
|
-
}
|
|
2702
|
+
const hostname = hostOf(uri);
|
|
2703
|
+
if (!hostname || !registrableDomain(hostname) || hostMatchesAnyDomain(hostname, AI_ENGINE_SELF_DOMAINS.chatgpt)) return null;
|
|
2704
|
+
return hostname;
|
|
2698
2705
|
}
|
|
2699
2706
|
async function generateText5(prompt, config) {
|
|
2700
2707
|
const model = config.model ?? DEFAULT_MODEL5;
|
|
@@ -3105,7 +3112,7 @@ import crypto4 from "crypto";
|
|
|
3105
3112
|
import fs2 from "fs";
|
|
3106
3113
|
import path3 from "path";
|
|
3107
3114
|
import os3 from "os";
|
|
3108
|
-
import { and as and2, eq as eq2, inArray, sql as sql2 } from "drizzle-orm";
|
|
3115
|
+
import { and as and2, eq as eq2, inArray, ne, sql as sql2 } from "drizzle-orm";
|
|
3109
3116
|
|
|
3110
3117
|
// src/run-telemetry.ts
|
|
3111
3118
|
import crypto2 from "crypto";
|
|
@@ -3312,10 +3319,18 @@ function classifyOneProviderError(message) {
|
|
|
3312
3319
|
return "UNKNOWN";
|
|
3313
3320
|
}
|
|
3314
3321
|
var JobRunner = class {
|
|
3322
|
+
/**
|
|
3323
|
+
* Invoked exactly when `activation.completed` is emitted: the project's
|
|
3324
|
+
* first non-empty, non-probe answer-visibility result. The serve process
|
|
3325
|
+
* uses it to thank the operator once; it is a UX hook, not telemetry, so it
|
|
3326
|
+
* fires (and is given) independently of whether telemetry is enabled.
|
|
3327
|
+
*/
|
|
3328
|
+
onFirstActivation;
|
|
3315
3329
|
db;
|
|
3316
3330
|
registry;
|
|
3317
3331
|
onRunCompleted;
|
|
3318
|
-
constructor(db, registry) {
|
|
3332
|
+
constructor(db, registry, opts) {
|
|
3333
|
+
this.onFirstActivation = opts?.onFirstActivation;
|
|
3319
3334
|
this.db = db;
|
|
3320
3335
|
this.registry = registry;
|
|
3321
3336
|
}
|
|
@@ -3587,6 +3602,19 @@ var JobRunner = class {
|
|
|
3587
3602
|
}),
|
|
3588
3603
|
failureCode ? { errorCode: failureCode } : void 0
|
|
3589
3604
|
);
|
|
3605
|
+
if (existingRun.kind === "answer-visibility" && runTrigger !== "probe" && totalSnapshotsInserted > 0 && !this.hasPriorActivation(projectId, runId)) {
|
|
3606
|
+
trackEvent("activation.completed", {
|
|
3607
|
+
flowVersion: ONBOARDING_FLOW_VERSION,
|
|
3608
|
+
status: finalStatus,
|
|
3609
|
+
providerCountBucket: bucketOnboardingCount(executionContext.providerCount),
|
|
3610
|
+
queryCountBucket: bucketOnboardingCount(executionContext.queryCount),
|
|
3611
|
+
snapshotCountBucket: bucketOnboardingCount(totalSnapshotsInserted)
|
|
3612
|
+
});
|
|
3613
|
+
try {
|
|
3614
|
+
this.onFirstActivation?.();
|
|
3615
|
+
} catch {
|
|
3616
|
+
}
|
|
3617
|
+
}
|
|
3590
3618
|
this.incrementUsage(projectId, "runs", 1);
|
|
3591
3619
|
if (this.onRunCompleted) {
|
|
3592
3620
|
this.onRunCompleted(runId, projectId).catch((err) => {
|
|
@@ -3675,8 +3703,18 @@ var JobRunner = class {
|
|
|
3675
3703
|
}
|
|
3676
3704
|
providerReservations.clear();
|
|
3677
3705
|
}
|
|
3706
|
+
hasPriorActivation(projectId, currentRunId) {
|
|
3707
|
+
return this.db.select({ id: querySnapshots.id }).from(querySnapshots).innerJoin(runs, eq2(querySnapshots.runId, runs.id)).where(and2(
|
|
3708
|
+
eq2(runs.projectId, projectId),
|
|
3709
|
+
eq2(runs.kind, "answer-visibility"),
|
|
3710
|
+
inArray(runs.status, ["completed", "partial"]),
|
|
3711
|
+
ne(runs.trigger, "probe"),
|
|
3712
|
+
ne(runs.id, currentRunId)
|
|
3713
|
+
)).limit(1).get() !== void 0;
|
|
3714
|
+
}
|
|
3678
3715
|
getRunState(runId) {
|
|
3679
3716
|
return this.db.select({
|
|
3717
|
+
kind: runs.kind,
|
|
3680
3718
|
status: runs.status,
|
|
3681
3719
|
finishedAt: runs.finishedAt,
|
|
3682
3720
|
error: runs.error,
|
|
@@ -3731,6 +3769,21 @@ function buildPhases(input) {
|
|
|
3731
3769
|
return { setup_ms, provider_call_ms, total_ms };
|
|
3732
3770
|
}
|
|
3733
3771
|
|
|
3772
|
+
// src/activation-notice.ts
|
|
3773
|
+
var ACTIVATION_NOTICE = "\n\u2605 First sweep finished. Your AI visibility baseline is in the dashboard.\n If Canonry is useful, a GitHub star helps others find it:\n https://github.com/Canonry/canonry\n\n";
|
|
3774
|
+
function maybeShowActivationNotice(io = {}) {
|
|
3775
|
+
const isTTY = io.isTTY ?? Boolean(process.stderr.isTTY);
|
|
3776
|
+
const write = io.write ?? ((line) => process.stderr.write(line));
|
|
3777
|
+
try {
|
|
3778
|
+
if (!isTTY) return;
|
|
3779
|
+
const raw = loadConfigRaw();
|
|
3780
|
+
if (!raw || raw.activationNoticeShown === true) return;
|
|
3781
|
+
saveConfigPatch({ activationNoticeShown: true });
|
|
3782
|
+
write(ACTIVATION_NOTICE);
|
|
3783
|
+
} catch {
|
|
3784
|
+
}
|
|
3785
|
+
}
|
|
3786
|
+
|
|
3734
3787
|
// src/gsc-sync.ts
|
|
3735
3788
|
import crypto5 from "crypto";
|
|
3736
3789
|
import { eq as eq3, and as and3, sql as sql3 } from "drizzle-orm";
|
|
@@ -5930,7 +5983,7 @@ function aggregateInboundLinksByDomain(links, targetDomain) {
|
|
|
5930
5983
|
for (const link of links) {
|
|
5931
5984
|
const host = hostOf(link.Url);
|
|
5932
5985
|
if (!host) continue;
|
|
5933
|
-
if (target && (host
|
|
5986
|
+
if (target && hostMatchesDomain(host, target)) continue;
|
|
5934
5987
|
let urls = byHost.get(host);
|
|
5935
5988
|
if (!urls) {
|
|
5936
5989
|
urls = /* @__PURE__ */ new Set();
|
|
@@ -7066,7 +7119,7 @@ function readStoredGroundingSources(rawResponse) {
|
|
|
7066
7119
|
return result;
|
|
7067
7120
|
}
|
|
7068
7121
|
async function backfillInsightsCommand(project, opts) {
|
|
7069
|
-
const { IntelligenceService: IntelligenceService2 } = await import("./intelligence-service-
|
|
7122
|
+
const { IntelligenceService: IntelligenceService2 } = await import("./intelligence-service-JEFHUGVX.js");
|
|
7070
7123
|
const config = loadConfig();
|
|
7071
7124
|
const db = createClient(config.database);
|
|
7072
7125
|
migrate(db);
|
|
@@ -10899,16 +10952,6 @@ var FETCH_TIMEOUT_MS2 = 1e4;
|
|
|
10899
10952
|
var MAX_TEXT_LENGTH = 4e3;
|
|
10900
10953
|
var MAX_BODY_BYTES = 512e3;
|
|
10901
10954
|
var USER_AGENT = "Canonry/1.0 (site-analysis)";
|
|
10902
|
-
function extractHostname(domain) {
|
|
10903
|
-
let hostname = domain;
|
|
10904
|
-
try {
|
|
10905
|
-
if (hostname.includes("://")) {
|
|
10906
|
-
hostname = new URL(hostname).hostname;
|
|
10907
|
-
}
|
|
10908
|
-
} catch {
|
|
10909
|
-
}
|
|
10910
|
-
return hostname.replace(/^www\./, "");
|
|
10911
|
-
}
|
|
10912
10955
|
function fetchWithPinnedAddress(target) {
|
|
10913
10956
|
return new Promise((resolve) => {
|
|
10914
10957
|
const port = target.url.port ? Number(target.url.port) : 443;
|
|
@@ -10965,7 +11008,8 @@ function fetchWithPinnedAddress(target) {
|
|
|
10965
11008
|
});
|
|
10966
11009
|
}
|
|
10967
11010
|
async function fetchSiteText(domain) {
|
|
10968
|
-
const hostname =
|
|
11011
|
+
const hostname = hostOf(domain);
|
|
11012
|
+
if (!hostname) return "";
|
|
10969
11013
|
const url = `https://${hostname}`;
|
|
10970
11014
|
const targetCheck = await resolveWebhookTarget(url);
|
|
10971
11015
|
if (!targetCheck.ok) return "";
|
|
@@ -11084,7 +11128,7 @@ var SnapshotService = class {
|
|
|
11084
11128
|
registry;
|
|
11085
11129
|
async createReport(input) {
|
|
11086
11130
|
const companyName = input.companyName.trim();
|
|
11087
|
-
const domain =
|
|
11131
|
+
const domain = hostOf(input.domain) ?? input.domain.trim();
|
|
11088
11132
|
const manualQueries = normalizeStringList(resolveSnapshotRequestQueries(input));
|
|
11089
11133
|
const manualCompetitors = normalizeStringList(input.competitors ?? []);
|
|
11090
11134
|
const providers = this.registry.getAll();
|
|
@@ -11092,14 +11136,14 @@ var SnapshotService = class {
|
|
|
11092
11136
|
throw new Error("No providers configured. Add at least one provider API key before running canonry snapshot.");
|
|
11093
11137
|
}
|
|
11094
11138
|
const analysisProvider = pickAnalysisProvider(this.registry.getApiProviders());
|
|
11095
|
-
const homepageUrl = `https://${
|
|
11139
|
+
const homepageUrl = `https://${domain}`;
|
|
11096
11140
|
const [siteText, audit] = await Promise.all([
|
|
11097
11141
|
fetchSiteText(domain),
|
|
11098
11142
|
this.runAudit(homepageUrl)
|
|
11099
11143
|
]);
|
|
11100
11144
|
if (manualQueries.length === 0 && !siteText) {
|
|
11101
11145
|
throw new Error(
|
|
11102
|
-
`Could not analyze https://${
|
|
11146
|
+
`Could not analyze https://${domain}. Try again with a reachable homepage or pass manual category queries via --queries.`
|
|
11103
11147
|
);
|
|
11104
11148
|
}
|
|
11105
11149
|
const profile = await this.buildProfile({
|
|
@@ -11501,36 +11545,30 @@ function buildFallbackRecommendedActions(audit) {
|
|
|
11501
11545
|
return uniqueStrings([...weakestFactors, ...defaults]).slice(0, 4);
|
|
11502
11546
|
}
|
|
11503
11547
|
function citesTargetDomain(citedDomains, groundingSources, targetDomain) {
|
|
11504
|
-
const normalizedTarget = extractHostname2(targetDomain);
|
|
11505
11548
|
for (const domain of citedDomains) {
|
|
11506
|
-
if (
|
|
11549
|
+
if (hostMatchesDomain(domain, targetDomain)) {
|
|
11507
11550
|
return true;
|
|
11508
11551
|
}
|
|
11509
11552
|
}
|
|
11510
11553
|
for (const source of groundingSources) {
|
|
11511
|
-
if (source.uri
|
|
11512
|
-
|
|
11513
|
-
}
|
|
11514
|
-
if (source.title && source.title.toLowerCase().includes(normalizedTarget.toLowerCase())) {
|
|
11515
|
-
return true;
|
|
11516
|
-
}
|
|
11554
|
+
if (hostMatchesDomain(source.uri, targetDomain)) return true;
|
|
11555
|
+
if (source.title && hostMatchesDomain(source.title, targetDomain)) return true;
|
|
11517
11556
|
}
|
|
11518
11557
|
return false;
|
|
11519
11558
|
}
|
|
11520
11559
|
function extractCompetitorsFromResponse(ctx) {
|
|
11521
11560
|
const competitors2 = /* @__PURE__ */ new Set();
|
|
11522
|
-
const
|
|
11523
|
-
const targetDomain = extractHostname2(ctx.targetDomain);
|
|
11561
|
+
const targetDomain = hostOf(ctx.targetDomain) ?? ctx.targetDomain;
|
|
11524
11562
|
for (const hint of ctx.manualCompetitors) {
|
|
11525
11563
|
if (isDomainLike(hint)) {
|
|
11526
|
-
const normalizedHint =
|
|
11527
|
-
if (
|
|
11528
|
-
if (ctx.citedDomains.some((domain) =>
|
|
11564
|
+
const normalizedHint = hostOf(hint) ?? hint;
|
|
11565
|
+
if (hostMatchesDomain(normalizedHint, targetDomain)) continue;
|
|
11566
|
+
if (ctx.citedDomains.some((domain) => hostMatchesDomain(domain, normalizedHint)) || textContainsDomain(ctx.answerText, normalizedHint)) {
|
|
11529
11567
|
competitors2.add(normalizedHint);
|
|
11530
11568
|
}
|
|
11531
11569
|
continue;
|
|
11532
11570
|
}
|
|
11533
|
-
if (hint.length >= 3 &&
|
|
11571
|
+
if (hint.length >= 3 && textContainsBrandAlias(ctx.answerText, hint)) {
|
|
11534
11572
|
competitors2.add(hint);
|
|
11535
11573
|
}
|
|
11536
11574
|
}
|
|
@@ -11579,27 +11617,8 @@ function uniqueStrings(values) {
|
|
|
11579
11617
|
values.filter((value) => typeof value === "string").map((value) => value.trim()).filter(Boolean)
|
|
11580
11618
|
)];
|
|
11581
11619
|
}
|
|
11582
|
-
function normalizeDomain(value) {
|
|
11583
|
-
const trimmed = value.trim();
|
|
11584
|
-
if (!trimmed) return trimmed;
|
|
11585
|
-
try {
|
|
11586
|
-
const url = trimmed.includes("://") ? new URL(trimmed) : new URL(`https://${trimmed}`);
|
|
11587
|
-
return url.hostname.replace(/^www\./, "").toLowerCase();
|
|
11588
|
-
} catch {
|
|
11589
|
-
return trimmed.replace(/^https?:\/\//, "").replace(/\/.*$/, "").replace(/^www\./, "").toLowerCase();
|
|
11590
|
-
}
|
|
11591
|
-
}
|
|
11592
|
-
function extractHostname2(value) {
|
|
11593
|
-
return normalizeDomain(value);
|
|
11594
|
-
}
|
|
11595
|
-
function domainMatches(candidate, target) {
|
|
11596
|
-
const normalizedCandidate = normalizeDomain(candidate);
|
|
11597
|
-
const normalizedTarget = normalizeDomain(target);
|
|
11598
|
-
return normalizedCandidate === normalizedTarget || normalizedCandidate.endsWith(`.${normalizedTarget}`);
|
|
11599
|
-
}
|
|
11600
11620
|
function isDomainLike(value) {
|
|
11601
|
-
|
|
11602
|
-
return normalized.includes(".") && !normalized.includes(" ");
|
|
11621
|
+
return registrableDomain(value).length > 0;
|
|
11603
11622
|
}
|
|
11604
11623
|
function clipText(value, length) {
|
|
11605
11624
|
if (value.length <= length) return value;
|
|
@@ -11998,7 +12017,12 @@ async function createServer(opts) {
|
|
|
11998
12017
|
}
|
|
11999
12018
|
const port = opts.config.port ?? 4100;
|
|
12000
12019
|
const serverUrl = `http://localhost:${port}`;
|
|
12001
|
-
const jobRunner = new JobRunner(opts.db, registry
|
|
12020
|
+
const jobRunner = new JobRunner(opts.db, registry, {
|
|
12021
|
+
// The one-time first-sweep thank-you. Lives on the serve console because
|
|
12022
|
+
// runs execute here, including the foreground serve that init hands off
|
|
12023
|
+
// to. TTY-gated inside, so supervised deployments never see it.
|
|
12024
|
+
onFirstActivation: () => maybeShowActivationNotice()
|
|
12025
|
+
});
|
|
12002
12026
|
jobRunner.recoverStaleRuns();
|
|
12003
12027
|
const notifier = new Notifier(opts.db, serverUrl);
|
|
12004
12028
|
const intelligenceService = new IntelligenceService(opts.db);
|
|
@@ -13127,6 +13151,7 @@ async function createServer(opts) {
|
|
|
13127
13151
|
app.log.error({ runId, err }, "Job runner failed");
|
|
13128
13152
|
});
|
|
13129
13153
|
},
|
|
13154
|
+
getRunnableProviderNames: () => registry.getAll().map((provider) => provider.adapter.name),
|
|
13130
13155
|
onProviderUpdate: (providerName, apiKey, model, baseUrl, incomingQuota) => {
|
|
13131
13156
|
const name = providerName;
|
|
13132
13157
|
if (!adapterMap[name]) return null;
|
|
@@ -13264,6 +13289,10 @@ async function createServer(opts) {
|
|
|
13264
13289
|
saveConfigPatch(config);
|
|
13265
13290
|
opts.config.telemetry = enabled;
|
|
13266
13291
|
},
|
|
13292
|
+
recordOnboardingEvent: (event) => {
|
|
13293
|
+
const { event: eventName, eventId, ...properties } = event;
|
|
13294
|
+
trackEvent(eventName, properties, { source: "dashboard", eventId });
|
|
13295
|
+
},
|
|
13267
13296
|
onCdpConfigure: async (host, port2) => {
|
|
13268
13297
|
if (!opts.config.cdp) opts.config.cdp = {};
|
|
13269
13298
|
opts.config.cdp.host = host;
|
|
@@ -13502,6 +13531,7 @@ function parseQueryResponse(raw, count) {
|
|
|
13502
13531
|
}
|
|
13503
13532
|
|
|
13504
13533
|
export {
|
|
13534
|
+
trackCliCommandFinished,
|
|
13505
13535
|
setTelemetrySource,
|
|
13506
13536
|
isTelemetryEnabled,
|
|
13507
13537
|
getOrCreateAnonymousId,
|