@canonry/canonry 7.44.1 → 7.45.1
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/agent-workspace/skills/canonry/references/canonry-cli.md +10 -0
- package/assets/agent-workspace/skills/canonry/references/google-business-profile.md +3 -3
- package/assets/assets/{AuditHistoryPanel-CvANw3ds.js → AuditHistoryPanel-BJDLPf4D.js} +1 -1
- package/assets/assets/{BacklinksPage-C6x9ELjk.js → BacklinksPage-BDbYMh4s.js} +1 -1
- package/assets/assets/{HistoryPage-r36HX1cC.js → HistoryPage-B9c7cjV4.js} +1 -1
- package/assets/assets/{MeasurementPropertyPage-ComoCzR4.js → MeasurementPropertyPage-B2wDvzLz.js} +1 -1
- package/assets/assets/{ProjectPage-CIeU-buk.js → ProjectPage-Cgj5ZmRt.js} +1 -1
- package/assets/assets/{RunsPage-DI6eJATZ.js → RunsPage-DNJpOT2C.js} +1 -1
- package/assets/assets/{SettingsPage-Bw7GdXxs.js → SettingsPage-BJABQP0W.js} +1 -1
- package/assets/assets/{SiteHealthSection-C-JaNKpS.js → SiteHealthSection-Co-YnYL1.js} +3 -3
- package/assets/assets/{TrafficPage-Cnsp276v.js → TrafficPage-DGks1U43.js} +1 -1
- package/assets/assets/{TrafficSourceDetailPage-CBIaJk7e.js → TrafficSourceDetailPage-B9EC-koW.js} +1 -1
- package/assets/assets/{extract-error-message-C_Owa0n9.js → extract-error-message-BnQj6MRg.js} +1 -1
- package/assets/assets/{index-ZS9sSTMS.js → index-CQ2txiby.js} +3 -3
- package/assets/assets/{react-sigma_core.esm.min-ugyh-ooq.js → react-sigma_core.esm.min-DMBVhOEe.js} +1 -1
- package/assets/assets/{v2-overview-adapter-BLSrVQ7K.js → v2-overview-adapter-DUYXYO7p.js} +1 -1
- package/assets/index.html +1 -1
- package/dist/{chunk-SW54HXSZ.js → chunk-55QNMM7V.js} +3 -3
- package/dist/{chunk-5LY7EG4U.js → chunk-5A57TIOK.js} +2 -2
- package/dist/{chunk-GZ2KIONF.js → chunk-6333IHPS.js} +26 -0
- package/dist/{chunk-MPWBFKGK.js → chunk-E7BG7QGP.js} +87 -68
- package/dist/{chunk-JVBI6VBT.js → chunk-EJTCZGU3.js} +2 -2
- package/dist/{chunk-LK7O7RM6.js → chunk-GCBMBG2Y.js} +227 -41
- package/dist/{chunk-45Z5RJZW.js → chunk-KMMOYVAO.js} +3 -3
- package/dist/{chunk-3JVF3ULD.js → chunk-KZS5N3JH.js} +1 -1
- package/dist/{chunk-57MGI2UT.js → chunk-PIL3NHER.js} +25 -83
- package/dist/{chunk-G47XJVAS.js → chunk-XFBY4DJV.js} +101 -27
- package/dist/{cli-catalog-PNJEQV5H.js → cli-catalog-E45QWSSR.js} +3 -3
- package/dist/cli-commands-I5YOZVO6.js +15 -0
- package/dist/cli.js +10 -10
- package/dist/{demo-server-WRHR25HF.js → demo-server-2ZRO7T3Z.js} +5 -5
- package/dist/index.js +6 -6
- package/dist/{intelligence-service-QXXRUB2G.js → intelligence-service-JIFXDUUC.js} +2 -2
- package/dist/mcp.js +4 -4
- package/package.json +12 -12
- package/dist/cli-commands-D3JIQ2ID.js +0 -15
|
@@ -21,19 +21,19 @@ import {
|
|
|
21
21
|
saveConfig,
|
|
22
22
|
saveConfigPatch,
|
|
23
23
|
sentimentInstallReadiness
|
|
24
|
-
} from "./chunk-
|
|
24
|
+
} from "./chunk-5A57TIOK.js";
|
|
25
25
|
import {
|
|
26
26
|
computeFactorAverages,
|
|
27
27
|
deleteSiteCrawlGraphLayout,
|
|
28
28
|
persistSiteCrawlGraphLayout,
|
|
29
29
|
prepareSiteCrawlGraphLayout,
|
|
30
30
|
recordedSharePct
|
|
31
|
-
} from "./chunk-
|
|
31
|
+
} from "./chunk-EJTCZGU3.js";
|
|
32
32
|
import {
|
|
33
33
|
CanonryMcpToolNames,
|
|
34
34
|
PACKAGE_VERSION,
|
|
35
35
|
canonryMcpTools
|
|
36
|
-
} from "./chunk-
|
|
36
|
+
} from "./chunk-E7BG7QGP.js";
|
|
37
37
|
import {
|
|
38
38
|
CliError
|
|
39
39
|
} from "./chunk-HGMYEDAQ.js";
|
|
@@ -240,7 +240,7 @@ import {
|
|
|
240
240
|
webhookResponseReason,
|
|
241
241
|
webhookTargetRefusalReason,
|
|
242
242
|
writeAuditLog
|
|
243
|
-
} from "./chunk-
|
|
243
|
+
} from "./chunk-XFBY4DJV.js";
|
|
244
244
|
import {
|
|
245
245
|
AGENT_MEMORY_VALUE_MAX_BYTES,
|
|
246
246
|
AGENT_NONE,
|
|
@@ -255,6 +255,7 @@ import {
|
|
|
255
255
|
AppError,
|
|
256
256
|
BacklinkSources,
|
|
257
257
|
CANONRY_NPM_PACKAGE_URL,
|
|
258
|
+
CHECK_RUNTIME_ERROR_SUFFIX,
|
|
258
259
|
CITED_URL_CAPTURE_VERSION,
|
|
259
260
|
CcReleaseSyncStatuses,
|
|
260
261
|
CheckNotificationPolicies,
|
|
@@ -487,7 +488,7 @@ import {
|
|
|
487
488
|
winnabilityClassLabel,
|
|
488
489
|
withRetry,
|
|
489
490
|
withSkippedProviders
|
|
490
|
-
} from "./chunk-
|
|
491
|
+
} from "./chunk-6333IHPS.js";
|
|
491
492
|
|
|
492
493
|
// src/runtime-context.ts
|
|
493
494
|
function cliRuntimeContext(env = process.env, stdio = process) {
|
|
@@ -4695,7 +4696,7 @@ function extractDomainFromUri(uri) {
|
|
|
4695
4696
|
const url = new URL(uri);
|
|
4696
4697
|
const hostname = hostOf(uri);
|
|
4697
4698
|
if (!hostname || !registrableDomain(hostname)) return null;
|
|
4698
|
-
if (hostMatchesAnyDomain(hostname, AI_ENGINE_SELF_DOMAINS.
|
|
4699
|
+
if (hostMatchesAnyDomain(hostname, AI_ENGINE_SELF_DOMAINS.gemini)) {
|
|
4699
4700
|
return null;
|
|
4700
4701
|
}
|
|
4701
4702
|
if (hostname === VERTEX_AI_SEARCH_PROXY_DOMAIN) {
|
|
@@ -5775,7 +5776,7 @@ function extractCitedDomainsFromSources3(groundingSources) {
|
|
|
5775
5776
|
}
|
|
5776
5777
|
function extractDomainFromUri3(uri) {
|
|
5777
5778
|
const hostname = hostOf(uri);
|
|
5778
|
-
if (!hostname || !registrableDomain(hostname) || hostMatchesAnyDomain(hostname, AI_ENGINE_SELF_DOMAINS.
|
|
5779
|
+
if (!hostname || !registrableDomain(hostname) || hostMatchesAnyDomain(hostname, AI_ENGINE_SELF_DOMAINS.claude)) {
|
|
5779
5780
|
return null;
|
|
5780
5781
|
}
|
|
5781
5782
|
return hostname;
|
|
@@ -7415,7 +7416,7 @@ function extractCitedDomains2(groundingSources) {
|
|
|
7415
7416
|
}
|
|
7416
7417
|
function extractDomainFromUri4(uri) {
|
|
7417
7418
|
const hostname = hostOf(uri);
|
|
7418
|
-
if (!hostname || !registrableDomain(hostname) || hostMatchesAnyDomain(hostname, AI_ENGINE_SELF_DOMAINS.
|
|
7419
|
+
if (!hostname || !registrableDomain(hostname) || hostMatchesAnyDomain(hostname, AI_ENGINE_SELF_DOMAINS.perplexity)) return null;
|
|
7419
7420
|
return hostname;
|
|
7420
7421
|
}
|
|
7421
7422
|
async function generateText5(prompt, config) {
|
|
@@ -8368,7 +8369,7 @@ function nonBlank(value) {
|
|
|
8368
8369
|
return trimmed ? trimmed : void 0;
|
|
8369
8370
|
}
|
|
8370
8371
|
function resolveBuildCommit(env = process.env) {
|
|
8371
|
-
const embedded = true ? "
|
|
8372
|
+
const embedded = true ? "c90d2268388fe6f2d75f741feb52344ee37c1e77" : void 0;
|
|
8372
8373
|
return nonBlank(embedded) ?? nonBlank(env.CANONRY_COMMIT);
|
|
8373
8374
|
}
|
|
8374
8375
|
function resolveInstanceIdentity(env = process.env) {
|
|
@@ -18945,6 +18946,39 @@ function isNews(now, was) {
|
|
|
18945
18946
|
return was === void 0 || now.rank > was.rank || now.rank === was.rank && now.code !== was.code;
|
|
18946
18947
|
}
|
|
18947
18948
|
var breachCodes = (breaches) => [...new Set([...breaches].map((b) => b.code))];
|
|
18949
|
+
var URGENT_HEALTH_EVENTS = /* @__PURE__ */ new Set(["health.urgent", "health.urgent-resolved"]);
|
|
18950
|
+
function urgentReceiptGroups(subscribers, receipts) {
|
|
18951
|
+
const groups = /* @__PURE__ */ new Map();
|
|
18952
|
+
for (const subscriber of subscribers) {
|
|
18953
|
+
const { url } = subscriber.config;
|
|
18954
|
+
const receipt = receipts?.[subscriber.id];
|
|
18955
|
+
const notified = receipt?.url === url ? receipt.notified : null;
|
|
18956
|
+
const group = groups.get(notified);
|
|
18957
|
+
if (group) group.subscribers.push(subscriber);
|
|
18958
|
+
else groups.set(notified, { notified, subscribers: [subscriber] });
|
|
18959
|
+
}
|
|
18960
|
+
return [...groups.values()];
|
|
18961
|
+
}
|
|
18962
|
+
function recordUrgentReceipts(receipts, subscribers, notified) {
|
|
18963
|
+
for (const subscriber of subscribers) receipts[subscriber.id] = { url: subscriber.config.url, notified };
|
|
18964
|
+
}
|
|
18965
|
+
function doctorUrgentSummary(receipts) {
|
|
18966
|
+
const breaches = Object.values(receipts).flatMap((receipt) => receipt.notified ? parseBreaches(receipt.notified) : []);
|
|
18967
|
+
const summary = formatUrgentBreaches(indexBreaches(breaches, "id").values());
|
|
18968
|
+
return summary || null;
|
|
18969
|
+
}
|
|
18970
|
+
function formatUrgentBreaches(breaches) {
|
|
18971
|
+
return formatBreaches([...breaches].sort((a, b) => b.rank - a.rank || compareText(a.id ?? a.code, b.id ?? b.code) || compareText(a.code, b.code)));
|
|
18972
|
+
}
|
|
18973
|
+
function siteUrgentSummary(receipts) {
|
|
18974
|
+
return Object.values(receipts).find((receipt) => receipt.notified !== null)?.notified ?? null;
|
|
18975
|
+
}
|
|
18976
|
+
var HEALTH_URGENT_RESOLVED_CODE = "health.urgent.resolved";
|
|
18977
|
+
function receivesHealthEvent(listed, event) {
|
|
18978
|
+
const events = listed ?? [];
|
|
18979
|
+
if (URGENT_HEALTH_EVENTS.has(event)) return events.includes(event);
|
|
18980
|
+
return events.includes(event) || !events.some((e) => URGENT_HEALTH_EVENTS.has(e));
|
|
18981
|
+
}
|
|
18948
18982
|
var SITE_LIVENESS_FAILURES_TO_PAGE = 2;
|
|
18949
18983
|
var SITE_LIVENESS_MIN_PASS_GAP_MS = 4 * 6e4;
|
|
18950
18984
|
var SITE_LIVENESS_MAX_PASS_GAP_MS = 45 * 6e4;
|
|
@@ -19041,9 +19075,19 @@ var Notifier = class {
|
|
|
19041
19075
|
* the next pass, because a quiet clearing pass would otherwise never
|
|
19042
19076
|
* surface it.
|
|
19043
19077
|
*
|
|
19044
|
-
*
|
|
19045
|
-
*
|
|
19046
|
-
*
|
|
19078
|
+
* Urgent problems are tracked beside all of this, not inside it. A check
|
|
19079
|
+
* marks its own output `urgent`; when one starts, `health.urgent` goes to
|
|
19080
|
+
* the webhooks that list it, and `health.urgent-resolved` when the ones it
|
|
19081
|
+
* reported have cleared and none is known to be open. They keep their own
|
|
19082
|
+
* record of what a subscriber was told (`urgent_notified`) because they go
|
|
19083
|
+
* to different webhooks than the alert above and are accepted
|
|
19084
|
+
* independently: a refused urgent alert is owed again whether or not the
|
|
19085
|
+
* general one was delivered. The record exists only while a webhook
|
|
19086
|
+
* subscribes to urgent events.
|
|
19087
|
+
*
|
|
19088
|
+
* Returns the general event emitted, or null when none was sent. The return
|
|
19089
|
+
* value is what tests assert against, so "stayed quiet" is observable rather
|
|
19090
|
+
* than inferred from the absence of a webhook.
|
|
19047
19091
|
*/
|
|
19048
19092
|
async onHealthChecked(projectId, report) {
|
|
19049
19093
|
const project = this.db.select().from(projects).where(eq21(projects.id, projectId)).get();
|
|
@@ -19132,7 +19176,46 @@ var Notifier = class {
|
|
|
19132
19176
|
event = "health.degraded";
|
|
19133
19177
|
}
|
|
19134
19178
|
const cleared = before === null || noSignal ? null : goneFrom(new Map([...before, ...notified ?? []]));
|
|
19135
|
-
const
|
|
19179
|
+
const urgentItems = [];
|
|
19180
|
+
for (const c of failing) {
|
|
19181
|
+
if (c.urgent !== true) continue;
|
|
19182
|
+
urgentItems.push({ id: c.id, code: c.code, rank: rank(c.status), status: c.status, summary: c.summary, remediation: c.remediation ?? null });
|
|
19183
|
+
}
|
|
19184
|
+
for (const c of healthChecks) {
|
|
19185
|
+
const supersededCode = c.details?.supersededCode;
|
|
19186
|
+
if (c.status !== CheckStatuses.skipped || c.urgent !== true || typeof supersededCode !== "string") continue;
|
|
19187
|
+
const causeIds = Array.isArray(c.details?.supersededBy) ? c.details.supersededBy : [];
|
|
19188
|
+
const cause = failing.find((f) => causeIds.includes(f.id));
|
|
19189
|
+
const status2 = c.details?.supersededStatus === "warn" ? "warn" : "fail";
|
|
19190
|
+
urgentItems.push({ id: c.id, code: supersededCode, rank: rank(status2), status: status2, summary: cause?.summary ?? c.summary, remediation: cause?.remediation ?? null });
|
|
19191
|
+
}
|
|
19192
|
+
urgentItems.sort((a, b) => b.rank - a.rank);
|
|
19193
|
+
const urgentById = new Map(urgentItems.map((item) => [item.id, item]));
|
|
19194
|
+
const generalEvent = event;
|
|
19195
|
+
const urgentRecord = previous?.urgentNotified ?? null;
|
|
19196
|
+
const enabled = generalEvent || urgentItems.length > 0 || urgentRecord ? this.db.select().from(notifications).where(eq21(notifications.projectId, projectId)).all().filter((n) => n.enabled) : [];
|
|
19197
|
+
const listedEvents = (notif) => notif.config.events;
|
|
19198
|
+
const hasUrl = (notif) => Boolean(notif.config.url);
|
|
19199
|
+
const urgentSubscribers = enabled.filter((n) => hasUrl(n) && (listedEvents(n) ?? []).some((e) => URGENT_HEALTH_EVENTS.has(e)));
|
|
19200
|
+
const conclusive = new Set(graded.filter((c) => !c.code.endsWith(CHECK_RUNTIME_ERROR_SUFFIX)).map((c) => c.id));
|
|
19201
|
+
const urgentReceipts = {};
|
|
19202
|
+
const recordedReceipts = previous?.urgentReceipts;
|
|
19203
|
+
const validReceipts = recordedReceipts && doctorUrgentSummary(recordedReceipts) === urgentRecord ? recordedReceipts : null;
|
|
19204
|
+
const urgentGroups = urgentReceiptGroups(urgentSubscribers, validReceipts).map((group) => {
|
|
19205
|
+
const urgentTold = group.notified ? parseBreaches(group.notified).filter((b) => b.id !== null) : [];
|
|
19206
|
+
const urgentCleared = urgentTold.filter((b) => conclusive.has(b.id) && !urgentById.has(b.id));
|
|
19207
|
+
const urgentUnknown = urgentTold.filter((b) => !conclusive.has(b.id) && !urgentById.has(b.id));
|
|
19208
|
+
const urgentOpen = urgentTold.filter((b) => urgentById.has(b.id) || hidden.has(b.id));
|
|
19209
|
+
const toldIds = new Set(urgentTold.map((b) => b.id));
|
|
19210
|
+
const urgentNew = urgentItems.filter((item) => !toldIds.has(item.id));
|
|
19211
|
+
const urgentEvent = urgentNew.length > 0 ? "health.urgent" : urgentCleared.length > 0 && urgentOpen.length === 0 ? "health.urgent-resolved" : null;
|
|
19212
|
+
const urgentNotifs = urgentEvent ? group.subscribers.filter((n) => receivesHealthEvent(listedEvents(n), urgentEvent)) : [];
|
|
19213
|
+
const urgentSettled = urgentEvent === "health.urgent-resolved" ? [] : [...urgentUnknown, ...urgentItems];
|
|
19214
|
+
const urgentBefore = urgentEvent === null ? [...urgentUnknown, ...urgentItems.filter((item) => toldIds.has(item.id))] : urgentNotifs.length > 0 ? urgentTold : urgentSettled;
|
|
19215
|
+
recordUrgentReceipts(urgentReceipts, group.subscribers, formatUrgentBreaches(urgentBefore) || null);
|
|
19216
|
+
return { ...group, urgentTold, urgentCleared, urgentNew, urgentEvent, urgentNotifs, urgentSettled };
|
|
19217
|
+
});
|
|
19218
|
+
const notifs = generalEvent ? enabled.filter((n) => receivesHealthEvent(listedEvents(n), generalEvent)) : [];
|
|
19136
19219
|
const willAttempt = notifs.some((n) => Boolean(n.config.url));
|
|
19137
19220
|
const triggerIds = new Set([
|
|
19138
19221
|
...newBreaches ?? failingNow.filter((b) => noSignal || b.id === worst?.id),
|
|
@@ -19149,6 +19232,8 @@ var Notifier = class {
|
|
|
19149
19232
|
failingSignature,
|
|
19150
19233
|
failingChecks: formatBreaches(failingNow),
|
|
19151
19234
|
pendingSignature: pendingNow === null ? null : formatBreaches(pendingNow),
|
|
19235
|
+
urgentNotified: doctorUrgentSummary(urgentReceipts),
|
|
19236
|
+
urgentReceipts,
|
|
19152
19237
|
// A delivered set an older release has written past is no longer what
|
|
19153
19238
|
// the operator was last told.
|
|
19154
19239
|
...idsKnown ? {} : { notifiedSignature: null }
|
|
@@ -19158,6 +19243,60 @@ var Notifier = class {
|
|
|
19158
19243
|
} else {
|
|
19159
19244
|
this.db.update(doctorHealthState).set(observation).where(eq21(doctorHealthState.projectId, projectId)).run();
|
|
19160
19245
|
}
|
|
19246
|
+
for (const group of urgentGroups) {
|
|
19247
|
+
const { urgentEvent, urgentTold, urgentNew, urgentCleared, urgentNotifs, urgentSettled } = group;
|
|
19248
|
+
if (!urgentEvent) continue;
|
|
19249
|
+
const toldRank = urgentTold.reduce((worstRank, b) => Math.max(worstRank, b.rank), 0);
|
|
19250
|
+
const previousUrgent = toldRank === 0 ? "ok" : toldRank === 2 ? "fail" : "warn";
|
|
19251
|
+
const lead = urgentNew[0];
|
|
19252
|
+
const onlyCleared = urgentCleared.length === 1 ? graded.find((c) => c.id === urgentCleared[0].id) : void 0;
|
|
19253
|
+
const resolvedSummary = onlyCleared ? onlyCleared.status === "ok" ? onlyCleared.summary : `No longer urgent. ${onlyCleared.summary}` : `${urgentCleared.length} urgent problems are resolved.`;
|
|
19254
|
+
const urgentHealth = urgentEvent === "health.urgent" && lead ? {
|
|
19255
|
+
status: lead.status,
|
|
19256
|
+
code: lead.code,
|
|
19257
|
+
summary: lead.summary,
|
|
19258
|
+
remediation: lead.remediation,
|
|
19259
|
+
checkedAt: now,
|
|
19260
|
+
previousStatus: previousUrgent,
|
|
19261
|
+
failing: urgentItems.map((item) => ({ id: item.id, status: item.status, code: item.code, summary: item.summary })),
|
|
19262
|
+
added: breachCodes(urgentNew),
|
|
19263
|
+
...urgentCleared.length > 0 ? { cleared: breachCodes(urgentCleared) } : {}
|
|
19264
|
+
} : {
|
|
19265
|
+
status: "ok",
|
|
19266
|
+
code: HEALTH_URGENT_RESOLVED_CODE,
|
|
19267
|
+
summary: resolvedSummary,
|
|
19268
|
+
remediation: null,
|
|
19269
|
+
checkedAt: now,
|
|
19270
|
+
previousStatus: previousUrgent,
|
|
19271
|
+
failing: [],
|
|
19272
|
+
cleared: breachCodes(urgentCleared)
|
|
19273
|
+
};
|
|
19274
|
+
const urgentPayload = {
|
|
19275
|
+
source: "canonry",
|
|
19276
|
+
event: urgentEvent,
|
|
19277
|
+
project: { name: project.name, canonicalDomain: project.canonicalDomain },
|
|
19278
|
+
health: urgentHealth,
|
|
19279
|
+
dashboardUrl: `${this.serverUrl}/projects/${project.name}`
|
|
19280
|
+
};
|
|
19281
|
+
let accepted = 0;
|
|
19282
|
+
for (const notif of urgentNotifs) {
|
|
19283
|
+
const config = notif.config;
|
|
19284
|
+
if (await this.sendWebhook(config.url, urgentPayload, notif.id, projectId, notif.webhookSecret ?? null)) accepted += 1;
|
|
19285
|
+
}
|
|
19286
|
+
if (accepted > 0) {
|
|
19287
|
+
recordUrgentReceipts(urgentReceipts, group.subscribers, formatUrgentBreaches(urgentSettled) || null);
|
|
19288
|
+
this.db.update(doctorHealthState).set({ urgentNotified: doctorUrgentSummary(urgentReceipts), urgentReceipts }).where(eq21(doctorHealthState.projectId, projectId)).run();
|
|
19289
|
+
}
|
|
19290
|
+
if (urgentNotifs.length > 0) {
|
|
19291
|
+
log21.info("health.urgent-notified", {
|
|
19292
|
+
projectId,
|
|
19293
|
+
event: urgentEvent,
|
|
19294
|
+
codes: urgentEvent === "health.urgent" ? breachCodes(urgentNew) : breachCodes(urgentCleared),
|
|
19295
|
+
subscribers: urgentNotifs.length,
|
|
19296
|
+
delivered: accepted
|
|
19297
|
+
});
|
|
19298
|
+
}
|
|
19299
|
+
}
|
|
19161
19300
|
if (!event) {
|
|
19162
19301
|
if (clearedThisPass.length > 0 || easedThisPass.length > 0) {
|
|
19163
19302
|
log21.info("health.improved", { projectId, status, code, cleared: clearedThisPass, eased: easedThisPass });
|
|
@@ -19233,6 +19372,27 @@ var Notifier = class {
|
|
|
19233
19372
|
let event = null;
|
|
19234
19373
|
if (check.status === "fail" && failures >= SITE_LIVENESS_FAILURES_TO_PAGE && !outagePaged) event = "health.degraded";
|
|
19235
19374
|
if (check.status === "ok" && outagePaged) event = "health.recovered";
|
|
19375
|
+
const confirmed = check.status === "fail" && failures >= SITE_LIVENESS_FAILURES_TO_PAGE;
|
|
19376
|
+
const urgentRecord = previous?.urgentNotifiedAt ?? null;
|
|
19377
|
+
const enabled = event !== null || confirmed || urgentRecord !== null ? this.db.select().from(notifications).where(eq21(notifications.projectId, projectId)).all().filter((n) => n.enabled) : [];
|
|
19378
|
+
const listedEvents = (notif) => notif.config.events;
|
|
19379
|
+
const hasUrl = (notif) => Boolean(notif.config.url);
|
|
19380
|
+
const scoped = enabled.filter((n) => hasUrl(n) && (listedEvents(n) ?? []).some((e) => URGENT_HEALTH_EVENTS.has(e)));
|
|
19381
|
+
const urgentReceipts = {};
|
|
19382
|
+
const recordedReceipts = previous?.urgentReceipts;
|
|
19383
|
+
const validReceipts = recordedReceipts && siteUrgentSummary(recordedReceipts) === urgentRecord ? recordedReceipts : null;
|
|
19384
|
+
const urgentGroups = urgentReceiptGroups(scoped, validReceipts).map((group) => {
|
|
19385
|
+
const urgentEvent = confirmed && group.notified === null ? "health.urgent" : check.status === "ok" && group.notified !== null ? "health.urgent-resolved" : null;
|
|
19386
|
+
const recipients = urgentEvent ? group.subscribers.filter((n) => receivesHealthEvent(listedEvents(n), urgentEvent)) : [];
|
|
19387
|
+
const generalName = urgentEvent === "health.urgent-resolved" ? "health.recovered" : "health.degraded";
|
|
19388
|
+
const generalRecipients = recipients.filter((n) => event === generalName && receivesHealthEvent(listedEvents(n), generalName));
|
|
19389
|
+
const generalIds = new Set(generalRecipients.map((n) => n.id));
|
|
19390
|
+
const urgentNotifs = recipients.filter((n) => !generalIds.has(n.id));
|
|
19391
|
+
const settled = urgentEvent === "health.urgent" ? checkedAt : null;
|
|
19392
|
+
const before = urgentEvent && recipients.length === 0 ? settled : group.notified;
|
|
19393
|
+
recordUrgentReceipts(urgentReceipts, group.subscribers, before);
|
|
19394
|
+
return { ...group, urgentEvent, urgentNotifs, generalIds, settled };
|
|
19395
|
+
});
|
|
19236
19396
|
const observation = {
|
|
19237
19397
|
projectId,
|
|
19238
19398
|
status: check.status,
|
|
@@ -19240,43 +19400,69 @@ var Notifier = class {
|
|
|
19240
19400
|
summary: check.summary,
|
|
19241
19401
|
consecutiveFailures: failures,
|
|
19242
19402
|
checkedAt,
|
|
19243
|
-
notifiedAt: check.status === "ok" ? null : previous?.notifiedAt ?? null
|
|
19403
|
+
notifiedAt: check.status === "ok" ? null : previous?.notifiedAt ?? null,
|
|
19404
|
+
urgentNotifiedAt: siteUrgentSummary(urgentReceipts),
|
|
19405
|
+
urgentReceipts
|
|
19244
19406
|
};
|
|
19245
19407
|
if (previous === void 0) {
|
|
19246
19408
|
this.db.insert(siteLivenessState).values(observation).run();
|
|
19247
19409
|
} else {
|
|
19248
19410
|
this.db.update(siteLivenessState).set(observation).where(eq21(siteLivenessState.projectId, projectId)).run();
|
|
19249
19411
|
}
|
|
19250
|
-
if (!event) return null;
|
|
19251
|
-
const
|
|
19252
|
-
|
|
19253
|
-
|
|
19254
|
-
|
|
19255
|
-
|
|
19256
|
-
|
|
19257
|
-
|
|
19258
|
-
|
|
19259
|
-
|
|
19260
|
-
|
|
19261
|
-
|
|
19262
|
-
|
|
19263
|
-
|
|
19264
|
-
|
|
19265
|
-
|
|
19266
|
-
|
|
19267
|
-
|
|
19268
|
-
|
|
19412
|
+
if (!event && urgentGroups.every((group) => group.urgentEvent === null)) return null;
|
|
19413
|
+
const payloadFor = (name) => {
|
|
19414
|
+
const outage = name === "health.degraded" || name === "health.urgent";
|
|
19415
|
+
return {
|
|
19416
|
+
source: "canonry",
|
|
19417
|
+
event: name,
|
|
19418
|
+
project: { name: project.name, canonicalDomain: project.canonicalDomain },
|
|
19419
|
+
health: {
|
|
19420
|
+
status: check.status,
|
|
19421
|
+
code: check.code,
|
|
19422
|
+
summary: check.summary,
|
|
19423
|
+
remediation: check.remediation ?? null,
|
|
19424
|
+
checkedAt,
|
|
19425
|
+
// The counter resets on every ok, so a page always follows a site that was
|
|
19426
|
+
// answering. Echoing the row the first failed pass wrote would report
|
|
19427
|
+
// "fail -> fail" and show the operator no transition at all.
|
|
19428
|
+
previousStatus: outage ? "ok" : "fail",
|
|
19429
|
+
failing: outage ? [{ id: check.id, status: check.status, code: check.code, summary: check.summary }] : []
|
|
19430
|
+
},
|
|
19431
|
+
dashboardUrl: `${this.serverUrl}/projects/${project.name}`
|
|
19432
|
+
};
|
|
19269
19433
|
};
|
|
19270
|
-
|
|
19271
|
-
|
|
19272
|
-
const
|
|
19273
|
-
|
|
19274
|
-
|
|
19434
|
+
const generalAccepted = /* @__PURE__ */ new Set();
|
|
19435
|
+
if (event) {
|
|
19436
|
+
const notifs = enabled.filter((n) => hasUrl(n) && receivesHealthEvent(listedEvents(n), event));
|
|
19437
|
+
let delivered = 0;
|
|
19438
|
+
for (const notif of notifs) {
|
|
19439
|
+
const config = notif.config;
|
|
19440
|
+
if (await this.sendWebhook(config.url, payloadFor(event), notif.id, projectId, notif.webhookSecret ?? null)) {
|
|
19441
|
+
delivered += 1;
|
|
19442
|
+
generalAccepted.add(notif.id);
|
|
19443
|
+
}
|
|
19444
|
+
}
|
|
19445
|
+
if (event === "health.degraded" && delivered > 0) {
|
|
19446
|
+
this.db.update(siteLivenessState).set({ notifiedAt: checkedAt }).where(eq21(siteLivenessState.projectId, projectId)).run();
|
|
19447
|
+
}
|
|
19448
|
+
log21.info("site-liveness.notified", { projectId, event, code: check.code, subscribers: notifs.length, delivered });
|
|
19275
19449
|
}
|
|
19276
|
-
|
|
19277
|
-
|
|
19450
|
+
for (const group of urgentGroups) {
|
|
19451
|
+
const { urgentEvent, urgentNotifs, generalIds } = group;
|
|
19452
|
+
if (!urgentEvent) continue;
|
|
19453
|
+
let accepted = [...generalIds].filter((id) => generalAccepted.has(id)).length;
|
|
19454
|
+
for (const notif of urgentNotifs) {
|
|
19455
|
+
const config = notif.config;
|
|
19456
|
+
if (await this.sendWebhook(config.url, payloadFor(urgentEvent), notif.id, projectId, notif.webhookSecret ?? null)) accepted += 1;
|
|
19457
|
+
}
|
|
19458
|
+
if (accepted > 0) {
|
|
19459
|
+
recordUrgentReceipts(urgentReceipts, group.subscribers, group.settled);
|
|
19460
|
+
this.db.update(siteLivenessState).set({ urgentNotifiedAt: siteUrgentSummary(urgentReceipts), urgentReceipts }).where(eq21(siteLivenessState.projectId, projectId)).run();
|
|
19461
|
+
}
|
|
19462
|
+
if (urgentNotifs.length > 0 || generalIds.size > 0) {
|
|
19463
|
+
log21.info("site-liveness.urgent-notified", { projectId, event: urgentEvent, code: check.code, subscribers: urgentNotifs.length + generalIds.size, delivered: accepted });
|
|
19464
|
+
}
|
|
19278
19465
|
}
|
|
19279
|
-
log21.info("site-liveness.notified", { projectId, event, code: check.code, subscribers: notifs.length, delivered });
|
|
19280
19466
|
return event;
|
|
19281
19467
|
}
|
|
19282
19468
|
/**
|
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
resolveCliAccess,
|
|
5
5
|
resolveCliAccessForInvocation,
|
|
6
6
|
windowOptions
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-KZS5N3JH.js";
|
|
8
8
|
import {
|
|
9
9
|
CliError,
|
|
10
10
|
EXIT_USER_ERROR,
|
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
} from "./chunk-HGMYEDAQ.js";
|
|
13
13
|
import {
|
|
14
14
|
OperationAccesses
|
|
15
|
-
} from "./chunk-
|
|
15
|
+
} from "./chunk-6333IHPS.js";
|
|
16
16
|
|
|
17
17
|
// src/cli-catalog.ts
|
|
18
18
|
import { createRequire } from "module";
|
|
@@ -109,7 +109,7 @@ async function dispatchRegisteredCommand(args, fallbackFormat, specs) {
|
|
|
109
109
|
const accessValue = accessFilterFromArgs(argsWithoutHelp);
|
|
110
110
|
validateAccessFilter(accessValue);
|
|
111
111
|
if (fallbackFormat !== "text") {
|
|
112
|
-
const { printCliCatalog: printCliCatalog2 } = await import("./cli-catalog-
|
|
112
|
+
const { printCliCatalog: printCliCatalog2 } = await import("./cli-catalog-E45QWSSR.js");
|
|
113
113
|
const group2 = argsWithoutHelp.find((arg) => !arg.startsWith("--"));
|
|
114
114
|
printCliCatalog2(specs, fallbackFormat, group2, accessValue);
|
|
115
115
|
return true;
|
|
@@ -31,7 +31,7 @@ import {
|
|
|
31
31
|
trackFeatureCompleted,
|
|
32
32
|
trackIntegrationConnection,
|
|
33
33
|
waitForServerRuntimeStartup
|
|
34
|
-
} from "./chunk-
|
|
34
|
+
} from "./chunk-GCBMBG2Y.js";
|
|
35
35
|
import {
|
|
36
36
|
clearDashboardPassword,
|
|
37
37
|
configExists,
|
|
@@ -50,7 +50,7 @@ import {
|
|
|
50
50
|
saveConfigPatch,
|
|
51
51
|
shouldPrintServeSkillsNudge,
|
|
52
52
|
withBootstrapProviderDefaults
|
|
53
|
-
} from "./chunk-
|
|
53
|
+
} from "./chunk-5A57TIOK.js";
|
|
54
54
|
import {
|
|
55
55
|
emitJsonl,
|
|
56
56
|
getBoolean,
|
|
@@ -64,16 +64,20 @@ import {
|
|
|
64
64
|
resolveCliAccess,
|
|
65
65
|
stringOption,
|
|
66
66
|
unknownSubcommand
|
|
67
|
-
} from "./chunk-
|
|
67
|
+
} from "./chunk-KZS5N3JH.js";
|
|
68
68
|
import {
|
|
69
69
|
DEFAULT_DEMO_TRUSTED_PROXIES,
|
|
70
70
|
trustedProxiesProblem,
|
|
71
71
|
trustedProxyProblem
|
|
72
72
|
} from "./chunk-RB2F4D3Z.js";
|
|
73
73
|
import {
|
|
74
|
+
dedupeGscSitemapUrls,
|
|
74
75
|
measurementDraftOperationSchema,
|
|
75
|
-
|
|
76
|
-
|
|
76
|
+
requireGscSitemapUrls,
|
|
77
|
+
resolveDiscoveredGscSitemapUrls,
|
|
78
|
+
runMeasurementDraftAction,
|
|
79
|
+
submitGscSitemapBatches
|
|
80
|
+
} from "./chunk-E7BG7QGP.js";
|
|
77
81
|
import {
|
|
78
82
|
CANONRY_DISCORD_URL,
|
|
79
83
|
CANONRY_ISSUES_URL,
|
|
@@ -118,7 +122,7 @@ import {
|
|
|
118
122
|
rawEventSamples,
|
|
119
123
|
readMarketCompetitorNames,
|
|
120
124
|
runs
|
|
121
|
-
} from "./chunk-
|
|
125
|
+
} from "./chunk-XFBY4DJV.js";
|
|
122
126
|
import {
|
|
123
127
|
AdsDeliverySnapshotStatuses,
|
|
124
128
|
AdsHistoricalCampaignRollupStatuses,
|
|
@@ -274,7 +278,7 @@ import {
|
|
|
274
278
|
visibilityCompareSelectionSchema,
|
|
275
279
|
visibilityReportRequestSchema,
|
|
276
280
|
winnabilityClassSchema
|
|
277
|
-
} from "./chunk-
|
|
281
|
+
} from "./chunk-6333IHPS.js";
|
|
278
282
|
|
|
279
283
|
// src/cli-commands/sentiment.ts
|
|
280
284
|
import "zod";
|
|
@@ -582,7 +586,7 @@ function parseDemoTrustedProxies(values) {
|
|
|
582
586
|
async function demoCommand(options) {
|
|
583
587
|
const { host, port } = parseDemoListenOptions(options);
|
|
584
588
|
const trustProxy = parseDemoTrustedProxies(options.trustProxy);
|
|
585
|
-
const { createDemoServer } = await import("./demo-server-
|
|
589
|
+
const { createDemoServer } = await import("./demo-server-2ZRO7T3Z.js");
|
|
586
590
|
const app = await createDemoServer({ trustProxy });
|
|
587
591
|
let stopping = false;
|
|
588
592
|
const stop = () => {
|
|
@@ -996,7 +1000,7 @@ No DB writes performed. Re-run without --dry-run to apply.`);
|
|
|
996
1000
|
}
|
|
997
1001
|
}
|
|
998
1002
|
async function backfillInsightsCommand(project2, opts) {
|
|
999
|
-
const { IntelligenceService } = await import("./intelligence-service-
|
|
1003
|
+
const { IntelligenceService } = await import("./intelligence-service-JIFXDUUC.js");
|
|
1000
1004
|
const config = loadConfig();
|
|
1001
1005
|
const db = createClient(config.database);
|
|
1002
1006
|
migrate(db);
|
|
@@ -10575,11 +10579,8 @@ function sitemapSubmittedUrlTotal(contents) {
|
|
|
10575
10579
|
if (!contents?.length) return "?";
|
|
10576
10580
|
return contents.reduce((total, content) => total + (Number.parseInt(content.submitted, 10) || 0), 0);
|
|
10577
10581
|
}
|
|
10578
|
-
function dedupeSitemapUrls(urls) {
|
|
10579
|
-
return [...new Set(urls.map((url) => url.trim()).filter(Boolean))];
|
|
10580
|
-
}
|
|
10581
10582
|
async function submitGscSitemaps(client, project2, opts) {
|
|
10582
|
-
const explicitUrls =
|
|
10583
|
+
const explicitUrls = dedupeGscSitemapUrls(opts.sitemapUrls ?? []);
|
|
10583
10584
|
const modes = Number(explicitUrls.length > 0) + Number(Boolean(opts.configured)) + Number(Boolean(opts.all)) + Number(Boolean(opts.allFiles));
|
|
10584
10585
|
if (modes !== 1) {
|
|
10585
10586
|
throw new CliError({
|
|
@@ -10609,77 +10610,16 @@ async function submitGscSitemaps(client, project2, opts) {
|
|
|
10609
10610
|
details: { command: "google.submit-sitemap", project: project2 }
|
|
10610
10611
|
});
|
|
10611
10612
|
}
|
|
10612
|
-
sitemapUrls =
|
|
10613
|
+
sitemapUrls = dedupeGscSitemapUrls([sitemapUrl]);
|
|
10613
10614
|
} else if (opts.all || opts.allFiles) {
|
|
10614
|
-
|
|
10615
|
-
|
|
10616
|
-
|
|
10617
|
-
|
|
10618
|
-
|
|
10619
|
-
|
|
10620
|
-
const indexes = topLevel.sitemaps.filter((sitemap) => sitemap.isSitemapsIndex).map((sitemap) => sitemap.path);
|
|
10621
|
-
const children = [];
|
|
10622
|
-
for (let offset = 0; offset < indexes.length; offset += 4) {
|
|
10623
|
-
children.push(...await Promise.all(
|
|
10624
|
-
indexes.slice(offset, offset + 4).map((sitemapIndex) => client.gscSitemaps(project2, { sitemapIndex }))
|
|
10625
|
-
));
|
|
10626
|
-
}
|
|
10627
|
-
const expandedIndexUrls = children.flatMap(
|
|
10628
|
-
(result, index) => result.sitemaps.length > 0 ? result.sitemaps.map((sitemap) => sitemap.path) : [indexes[index]]
|
|
10629
|
-
);
|
|
10630
|
-
sitemapUrls = dedupeSitemapUrls([
|
|
10631
|
-
...topLevel.sitemaps.filter((sitemap) => !sitemap.isSitemapsIndex).map((sitemap) => sitemap.path),
|
|
10632
|
-
...expandedIndexUrls
|
|
10633
|
-
]);
|
|
10634
|
-
}
|
|
10635
|
-
if (sitemapUrls.length === 0) {
|
|
10636
|
-
throw new CliError({
|
|
10637
|
-
code: "CLI_USAGE_ERROR",
|
|
10638
|
-
message: "no GSC sitemaps found; provide a URL or use --configured",
|
|
10639
|
-
displayMessage: "Error: no GSC sitemaps found; provide a URL or use --configured",
|
|
10640
|
-
details: { command: "google.submit-sitemap", project: project2 }
|
|
10641
|
-
});
|
|
10642
|
-
}
|
|
10643
|
-
}
|
|
10644
|
-
const batches = opts.all || opts.allFiles ? Array.from({ length: Math.ceil(sitemapUrls.length / 50) }, (_, index) => sitemapUrls.slice(index * 50, index * 50 + 50)) : [sitemapUrls];
|
|
10645
|
-
const aggregate = {
|
|
10646
|
-
summary: { total: 0, accepted: 0, failed: 0 },
|
|
10647
|
-
results: []
|
|
10648
|
-
};
|
|
10649
|
-
for (const [index, batchSitemapUrls] of batches.entries()) {
|
|
10650
|
-
try {
|
|
10651
|
-
const result = await client.gscSubmitSitemaps(project2, { sitemapUrls: batchSitemapUrls });
|
|
10652
|
-
aggregate.summary.total += result.summary.total;
|
|
10653
|
-
aggregate.summary.accepted += result.summary.accepted;
|
|
10654
|
-
aggregate.summary.failed += result.summary.failed;
|
|
10655
|
-
aggregate.results.push(...result.results);
|
|
10656
|
-
opts.onProgress?.(index + 1, batches.length);
|
|
10657
|
-
} catch (cause) {
|
|
10658
|
-
if (index === 0) throw cause;
|
|
10659
|
-
const attempted = aggregate.summary.total + batchSitemapUrls.length;
|
|
10660
|
-
const remaining = sitemapUrls.length - attempted;
|
|
10661
|
-
const causeDetails = cause instanceof CliError ? { code: cause.code, message: cause.message, details: cause.details } : { message: describeError(cause) };
|
|
10662
|
-
throw new CliError({
|
|
10663
|
-
code: "GOOGLE_SITEMAP_SUBMISSION_PARTIAL",
|
|
10664
|
-
message: `Sitemap submission stopped at batch ${index + 1}/${batches.length}; ${aggregate.summary.accepted} accepted, ${aggregate.summary.failed} failed, ${batchSitemapUrls.length} unconfirmed, ${remaining} not attempted.`,
|
|
10665
|
-
displayMessage: `Sitemap submission stopped at batch ${index + 1}/${batches.length}; ${aggregate.summary.accepted} accepted, ${aggregate.summary.failed} failed, ${batchSitemapUrls.length} unconfirmed, ${remaining} not attempted. Earlier accepted submissions were not rolled back.`,
|
|
10666
|
-
exitCode: cause instanceof CliError && cause.exitCode === 1 ? 1 : EXIT_SYSTEM_ERROR,
|
|
10667
|
-
details: {
|
|
10668
|
-
project: project2,
|
|
10669
|
-
accepted: aggregate.summary.accepted,
|
|
10670
|
-
failed: aggregate.summary.failed,
|
|
10671
|
-
completed: aggregate.summary.total,
|
|
10672
|
-
attempted,
|
|
10673
|
-
unconfirmed: batchSitemapUrls.length,
|
|
10674
|
-
remaining,
|
|
10675
|
-
unconfirmedBatch: { index: index + 1, total: batches.length, sitemapUrls: batchSitemapUrls },
|
|
10676
|
-
partialResult: aggregate,
|
|
10677
|
-
cause: causeDetails
|
|
10678
|
-
}
|
|
10679
|
-
});
|
|
10680
|
-
}
|
|
10615
|
+
sitemapUrls = await resolveDiscoveredGscSitemapUrls(
|
|
10616
|
+
client,
|
|
10617
|
+
project2,
|
|
10618
|
+
opts.allFiles ? "all-files" : "indexes"
|
|
10619
|
+
);
|
|
10620
|
+
requireGscSitemapUrls(project2, sitemapUrls);
|
|
10681
10621
|
}
|
|
10682
|
-
return
|
|
10622
|
+
return submitGscSitemapBatches(client, project2, sitemapUrls, opts);
|
|
10683
10623
|
}
|
|
10684
10624
|
async function googleSubmitSitemaps(project2, opts) {
|
|
10685
10625
|
const client = getClient11();
|
|
@@ -13215,6 +13155,8 @@ var EVENT_DESCRIPTIONS = {
|
|
|
13215
13155
|
"insight.high": "A high-severity insight was generated",
|
|
13216
13156
|
"health.degraded": "A scheduled health check found the measurement degraded, daily data stopped arriving, or the website stopped answering (sent when a check starts failing or gets worse, not every pass)",
|
|
13217
13157
|
"health.recovered": "A degraded health check is passing again, or a website that was paged as down answers again",
|
|
13158
|
+
"health.urgent": "An urgent problem started: the website is down, GA4 or Search Console data is stale, or a sync keeps failing. A webhook that lists this receives only the health events it lists, so it is not sent routine warnings",
|
|
13159
|
+
"health.urgent-resolved": "The urgent problems that were reported have cleared (the website check and the scheduled health pass each send their own)",
|
|
13218
13160
|
"review.negative": "A new or edited Google review at or below the project's negative-review threshold, 3 stars by default (Business Profile reviews, or the public listing when Google has not enabled review access)",
|
|
13219
13161
|
"review.rating-dropped": "A location's public Google rating fell (sent for locations without Business Profile review access)"
|
|
13220
13162
|
};
|
|
@@ -19436,7 +19378,7 @@ var SYSTEM_CLI_COMMANDS = [
|
|
|
19436
19378
|
options: { access: stringOption() },
|
|
19437
19379
|
run: async (input) => {
|
|
19438
19380
|
if (input.positionals.length > 1) throw usageError("Expected at most one command group");
|
|
19439
|
-
const [{ printCliCatalog }, { REGISTERED_CLI_COMMANDS: REGISTERED_CLI_COMMANDS2 }] = await Promise.all([import("./cli-catalog-
|
|
19381
|
+
const [{ printCliCatalog }, { REGISTERED_CLI_COMMANDS: REGISTERED_CLI_COMMANDS2 }] = await Promise.all([import("./cli-catalog-E45QWSSR.js"), import("./cli-commands-I5YOZVO6.js")]);
|
|
19440
19382
|
printCliCatalog(REGISTERED_CLI_COMMANDS2, input.format, input.positionals[0], getString(input.values, "access"));
|
|
19441
19383
|
}
|
|
19442
19384
|
},
|