@aeptus/aexeo-emdash 0.8.18 → 0.8.19
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/CHANGELOG.md +104 -6
- package/INSTALL.md +13 -4
- package/LICENSE +0 -0
- package/README.md +0 -0
- package/dist/adapter.d.ts +0 -0
- package/dist/adapter.d.ts.map +0 -0
- package/dist/admin/Absorbability.d.ts +0 -0
- package/dist/admin/Absorbability.d.ts.map +0 -0
- package/dist/admin/Accessibility.d.ts +0 -0
- package/dist/admin/Accessibility.d.ts.map +0 -0
- package/dist/admin/Citability.d.ts +0 -0
- package/dist/admin/Citability.d.ts.map +0 -0
- package/dist/admin/EntityLegitimacy.d.ts +0 -0
- package/dist/admin/EntityLegitimacy.d.ts.map +0 -0
- package/dist/admin/EntityPresence.d.ts +0 -0
- package/dist/admin/EntityPresence.d.ts.map +0 -0
- package/dist/admin/Facts.d.ts +0 -0
- package/dist/admin/Facts.d.ts.map +0 -0
- package/dist/admin/Findings.d.ts +0 -0
- package/dist/admin/Findings.d.ts.map +0 -0
- package/dist/admin/PillarView.d.ts +0 -0
- package/dist/admin/PillarView.d.ts.map +0 -0
- package/dist/admin/Retrievability.d.ts +0 -0
- package/dist/admin/Retrievability.d.ts.map +0 -0
- package/dist/admin.d.ts +0 -0
- package/dist/admin.d.ts.map +0 -0
- package/dist/admin.js +0 -0
- package/dist/configured.d.ts +0 -0
- package/dist/configured.d.ts.map +1 -1
- package/dist/configured.js +331 -283
- package/dist/data-route.d.ts +0 -0
- package/dist/data-route.d.ts.map +0 -0
- package/dist/evaluator-sandbox.d.ts +0 -0
- package/dist/evaluator-sandbox.d.ts.map +0 -0
- package/dist/evaluator.d.ts +0 -0
- package/dist/evaluator.d.ts.map +0 -0
- package/dist/facts-route.d.ts +0 -0
- package/dist/facts-route.d.ts.map +0 -0
- package/dist/findings-view.d.ts +136 -0
- package/dist/findings-view.d.ts.map +1 -0
- package/dist/index.d.ts +0 -0
- package/dist/index.d.ts.map +0 -0
- package/dist/index.js +13 -2
- package/dist/plugin.d.ts +22 -2
- package/dist/plugin.d.ts.map +1 -1
- package/dist/presence-check.d.ts +0 -0
- package/dist/presence-check.d.ts.map +0 -0
- package/dist/presence-route.d.ts +0 -0
- package/dist/presence-route.d.ts.map +0 -0
- package/dist/sandbox-entry.d.ts +2 -22
- package/dist/sandbox-entry.d.ts.map +1 -1
- package/dist/sandbox-entry.js +410 -353
- package/dist/sandbox.d.ts +2 -0
- package/dist/sandbox.d.ts.map +1 -1
- package/dist/sidecar-url.d.ts +18 -0
- package/dist/sidecar-url.d.ts.map +1 -0
- package/dist/sidecar.d.ts +0 -0
- package/dist/sidecar.d.ts.map +0 -0
- package/dist/suppressions.d.ts +0 -0
- package/dist/suppressions.d.ts.map +0 -0
- package/dist/types.d.ts +0 -0
- package/dist/types.d.ts.map +0 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.d.ts.map +0 -0
- package/dist/wasm-init.d.ts +0 -0
- package/dist/wasm-init.d.ts.map +0 -0
- package/package.json +4 -2
- package/wasm/aexeo_emdash_bridge.d.ts +0 -0
- package/wasm/aexeo_emdash_bridge.js +0 -0
- package/wasm/aexeo_emdash_bridge_bg.d.ts +0 -0
- package/wasm/aexeo_emdash_bridge_bg.js +0 -0
- package/wasm/aexeo_emdash_bridge_bg.wasm +0 -0
- package/wasm/aexeo_emdash_bridge_bg.wasm.d.ts +0 -0
- package/dist/indexnow.d.ts +0 -15
- package/dist/indexnow.d.ts.map +0 -1
- package/dist/mcp.d.ts +0 -28
- package/dist/mcp.d.ts.map +0 -1
package/dist/configured.js
CHANGED
|
@@ -198,6 +198,14 @@ function isRecord(value) {
|
|
|
198
198
|
}
|
|
199
199
|
|
|
200
200
|
// src/plugin.ts
|
|
201
|
+
var SETTINGS_TOKEN_KEY = "eval_token";
|
|
202
|
+
var PLUGIN_SETTINGS_SCHEMA = {
|
|
203
|
+
[SETTINGS_TOKEN_KEY]: {
|
|
204
|
+
type: "secret",
|
|
205
|
+
label: "EVAL_TOKEN",
|
|
206
|
+
description: "Bearer token for your aexeo-crawl-worker Worker. Encrypted at rest by the emdash host."
|
|
207
|
+
}
|
|
208
|
+
};
|
|
201
209
|
async function handleAfterSaveConfigured(event, ctx, evaluator, suppressionFilter) {
|
|
202
210
|
const adapted = await adaptContentItemWithContext(
|
|
203
211
|
event.content,
|
|
@@ -394,6 +402,290 @@ async function readFindings(kv, route) {
|
|
|
394
402
|
return stored.findings;
|
|
395
403
|
}
|
|
396
404
|
|
|
405
|
+
// src/findings-view.ts
|
|
406
|
+
function normalizeInteraction(input) {
|
|
407
|
+
if (!input || typeof input !== "object") {
|
|
408
|
+
return { type: "page_load", page: "/findings" };
|
|
409
|
+
}
|
|
410
|
+
const raw = input;
|
|
411
|
+
if (raw.type === "page_load") {
|
|
412
|
+
return {
|
|
413
|
+
type: "page_load",
|
|
414
|
+
page: typeof raw.page === "string" && raw.page.length > 0 ? raw.page : "/findings"
|
|
415
|
+
};
|
|
416
|
+
}
|
|
417
|
+
if (raw.type === "block_action") {
|
|
418
|
+
return {
|
|
419
|
+
type: "block_action",
|
|
420
|
+
action_id: typeof raw.action_id === "string" ? raw.action_id : "",
|
|
421
|
+
...typeof raw.block_id === "string" ? { block_id: raw.block_id } : {},
|
|
422
|
+
value: raw.value
|
|
423
|
+
};
|
|
424
|
+
}
|
|
425
|
+
if (raw.type === "form_submit") {
|
|
426
|
+
return {
|
|
427
|
+
type: "form_submit",
|
|
428
|
+
action_id: typeof raw.action_id === "string" ? raw.action_id : "",
|
|
429
|
+
...typeof raw.block_id === "string" ? { block_id: raw.block_id } : {},
|
|
430
|
+
values: raw.values && typeof raw.values === "object" ? raw.values : {}
|
|
431
|
+
};
|
|
432
|
+
}
|
|
433
|
+
return { type: "page_load", page: "/findings" };
|
|
434
|
+
}
|
|
435
|
+
async function handleRefresh(options) {
|
|
436
|
+
let summary;
|
|
437
|
+
try {
|
|
438
|
+
summary = await evaluateAndPersistAll(options.ctx, {
|
|
439
|
+
collections: options.collections,
|
|
440
|
+
evaluator: options.evaluator,
|
|
441
|
+
// Always forwarded. See RefreshOptions.suppressionFilter.
|
|
442
|
+
suppressionFilter: options.suppressionFilter
|
|
443
|
+
});
|
|
444
|
+
} catch (err) {
|
|
445
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
446
|
+
const message = `Refresh failed: ${detail}`;
|
|
447
|
+
return {
|
|
448
|
+
blocks: [
|
|
449
|
+
{ type: "header", text: "SEO findings" },
|
|
450
|
+
{
|
|
451
|
+
type: "banner",
|
|
452
|
+
title: message,
|
|
453
|
+
variant: options.tuning.refreshFailureVariant
|
|
454
|
+
}
|
|
455
|
+
],
|
|
456
|
+
toast: { message, type: "error" }
|
|
457
|
+
};
|
|
458
|
+
}
|
|
459
|
+
const refreshed = await renderFindingsPage(options.ctx.kv, options.tuning);
|
|
460
|
+
const toastMessage = summary.errors.length === 0 ? `Refreshed ${summary.routesUpdated} routes (${summary.totalFindings} findings across ${summary.documentsScanned} documents)` : `Refresh completed with ${summary.errors.length} errors \u2014 see banner`;
|
|
461
|
+
if (summary.errors.length > 0) {
|
|
462
|
+
refreshed.blocks.unshift({
|
|
463
|
+
type: "banner",
|
|
464
|
+
title: `Refresh issues: ${summary.errors.join(" \u2022 ")}`,
|
|
465
|
+
variant: "alert"
|
|
466
|
+
});
|
|
467
|
+
}
|
|
468
|
+
return {
|
|
469
|
+
...refreshed,
|
|
470
|
+
toast: {
|
|
471
|
+
message: toastMessage,
|
|
472
|
+
type: summary.errors.length === 0 ? "success" : "info"
|
|
473
|
+
}
|
|
474
|
+
};
|
|
475
|
+
}
|
|
476
|
+
async function readAllFindings(kv) {
|
|
477
|
+
const entries = await kv.list(
|
|
478
|
+
"findings:"
|
|
479
|
+
);
|
|
480
|
+
const out = [];
|
|
481
|
+
for (const entry of entries) {
|
|
482
|
+
if (entry.value === null) {
|
|
483
|
+
continue;
|
|
484
|
+
}
|
|
485
|
+
const route = entry.key.replace(/^findings:/, "");
|
|
486
|
+
for (const finding of entry.value.findings) {
|
|
487
|
+
out.push({ ...finding, document_route: route });
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
return out;
|
|
491
|
+
}
|
|
492
|
+
function severityFirst(a, b) {
|
|
493
|
+
const rank = (severity) => severity === "error" ? 0 : 1;
|
|
494
|
+
const diff = rank(a.severity) - rank(b.severity);
|
|
495
|
+
if (diff !== 0) {
|
|
496
|
+
return diff;
|
|
497
|
+
}
|
|
498
|
+
return a.rule_id.localeCompare(b.rule_id);
|
|
499
|
+
}
|
|
500
|
+
function uniqueRoutes(rows) {
|
|
501
|
+
const routes = /* @__PURE__ */ new Set();
|
|
502
|
+
for (const row of rows) {
|
|
503
|
+
routes.add(row.document_route);
|
|
504
|
+
}
|
|
505
|
+
return [...routes].sort();
|
|
506
|
+
}
|
|
507
|
+
async function renderFindingsPage(kv, tuning) {
|
|
508
|
+
const findings = await readAllFindings(kv);
|
|
509
|
+
const errors = findings.filter((finding) => finding.severity === "error");
|
|
510
|
+
const warnings = findings.filter(
|
|
511
|
+
(finding) => finding.severity === "warning"
|
|
512
|
+
);
|
|
513
|
+
const sorted = [...findings].sort(severityFirst);
|
|
514
|
+
const routes = uniqueRoutes(findings);
|
|
515
|
+
const blocks = [
|
|
516
|
+
{ type: "header", text: "SEO findings" },
|
|
517
|
+
{
|
|
518
|
+
type: "context",
|
|
519
|
+
text: findings.length === 0 ? "No findings yet \u2014 click Refresh to evaluate the site." : `${findings.length} findings across ${routes.length} routes \u2014 ${errors.length} errors, ${warnings.length} warnings.`
|
|
520
|
+
},
|
|
521
|
+
{ type: "divider" },
|
|
522
|
+
{
|
|
523
|
+
type: "actions",
|
|
524
|
+
elements: [
|
|
525
|
+
// Refresh evaluates every configured entry. Pressing it lists
|
|
526
|
+
// content via the live in-request bridge, evaluates it, and
|
|
527
|
+
// writes findings back to KV (with suppressions applied).
|
|
528
|
+
{
|
|
529
|
+
type: "button",
|
|
530
|
+
label: "Refresh",
|
|
531
|
+
action_id: "refresh_findings",
|
|
532
|
+
style: "primary"
|
|
533
|
+
},
|
|
534
|
+
{ type: "button", label: "All", action_id: "filter:all" },
|
|
535
|
+
{ type: "button", label: "Errors only", action_id: "filter:errors" },
|
|
536
|
+
{
|
|
537
|
+
type: "button",
|
|
538
|
+
label: "Warnings only",
|
|
539
|
+
action_id: "filter:warnings"
|
|
540
|
+
}
|
|
541
|
+
]
|
|
542
|
+
},
|
|
543
|
+
sorted.length === 0 ? {
|
|
544
|
+
type: "context",
|
|
545
|
+
text: tuning.emptyFindingsText
|
|
546
|
+
} : findingsTable(sorted)
|
|
547
|
+
];
|
|
548
|
+
if (routes.length > 0) {
|
|
549
|
+
blocks.push({
|
|
550
|
+
type: "form",
|
|
551
|
+
fields: [
|
|
552
|
+
{
|
|
553
|
+
type: "select",
|
|
554
|
+
action_id: "route_picker",
|
|
555
|
+
label: "Document to inspect",
|
|
556
|
+
options: routes.map((route) => ({ label: route, value: route }))
|
|
557
|
+
}
|
|
558
|
+
],
|
|
559
|
+
submit: { label: "View document SEO", action_id: "view_document" }
|
|
560
|
+
});
|
|
561
|
+
}
|
|
562
|
+
return { blocks };
|
|
563
|
+
}
|
|
564
|
+
function findingsTable(rows) {
|
|
565
|
+
return {
|
|
566
|
+
type: "table",
|
|
567
|
+
columns: [
|
|
568
|
+
{ key: "route", label: "Route" },
|
|
569
|
+
{ key: "rule", label: "Rule", format: "code" },
|
|
570
|
+
{ key: "severity", label: "Severity", format: "badge" },
|
|
571
|
+
{ key: "message", label: "Message" }
|
|
572
|
+
],
|
|
573
|
+
rows: rows.map((row) => ({
|
|
574
|
+
route: row.document_route,
|
|
575
|
+
rule: row.rule_id,
|
|
576
|
+
severity: row.severity,
|
|
577
|
+
message: row.message
|
|
578
|
+
}))
|
|
579
|
+
};
|
|
580
|
+
}
|
|
581
|
+
async function renderScoreWidget(kv, tuning) {
|
|
582
|
+
const documents = await readAllDocuments(kv);
|
|
583
|
+
if (documents.length === 0) {
|
|
584
|
+
return {
|
|
585
|
+
blocks: [
|
|
586
|
+
{ type: "header", text: "SEO score" },
|
|
587
|
+
{
|
|
588
|
+
type: "context",
|
|
589
|
+
text: tuning.emptyScoreText
|
|
590
|
+
}
|
|
591
|
+
]
|
|
592
|
+
};
|
|
593
|
+
}
|
|
594
|
+
const score = await tuning.score(documents, kv);
|
|
595
|
+
const blocks = [
|
|
596
|
+
{
|
|
597
|
+
type: "stats",
|
|
598
|
+
items: [
|
|
599
|
+
{ label: "Overall", value: `${score.overall_score}` },
|
|
600
|
+
{ label: "Citation", value: `${score.citation_readiness_score}` },
|
|
601
|
+
{
|
|
602
|
+
label: tuning.truthLabel(score),
|
|
603
|
+
value: `${score.truth_consistency_score}`
|
|
604
|
+
},
|
|
605
|
+
{ label: "Answers", value: `${score.answer_pack_score}` }
|
|
606
|
+
]
|
|
607
|
+
}
|
|
608
|
+
];
|
|
609
|
+
if (score.overall_score < 60) {
|
|
610
|
+
blocks.unshift({
|
|
611
|
+
type: "banner",
|
|
612
|
+
title: `Site score is ${score.overall_score} \u2014 below the 60 quality threshold`,
|
|
613
|
+
variant: "alert"
|
|
614
|
+
});
|
|
615
|
+
}
|
|
616
|
+
if (score.blockers.length > 0) {
|
|
617
|
+
blocks.push({
|
|
618
|
+
type: "context",
|
|
619
|
+
text: topBlockersLine(score)
|
|
620
|
+
});
|
|
621
|
+
}
|
|
622
|
+
return { blocks };
|
|
623
|
+
}
|
|
624
|
+
function topBlockersLine(score) {
|
|
625
|
+
const top = score.blockers.slice(0, 3).map((blocker) => blocker.message);
|
|
626
|
+
if (top.length === 0) {
|
|
627
|
+
return "No blockers identified.";
|
|
628
|
+
}
|
|
629
|
+
return `Top blockers: ${top.join(" \u2022 ")}`;
|
|
630
|
+
}
|
|
631
|
+
async function renderDocumentPanel(kv, route) {
|
|
632
|
+
if (route === void 0) {
|
|
633
|
+
return {
|
|
634
|
+
blocks: [
|
|
635
|
+
{ type: "header", text: "Document SEO" },
|
|
636
|
+
{
|
|
637
|
+
type: "context",
|
|
638
|
+
text: "Pick a document from the SEO findings list to see its rule findings here."
|
|
639
|
+
}
|
|
640
|
+
]
|
|
641
|
+
};
|
|
642
|
+
}
|
|
643
|
+
const findings = await readFindings(kv, route);
|
|
644
|
+
const errors = findings.filter((finding) => finding.severity === "error");
|
|
645
|
+
const warnings = findings.filter(
|
|
646
|
+
(finding) => finding.severity === "warning"
|
|
647
|
+
);
|
|
648
|
+
const sorted = [...findings].sort(severityFirst);
|
|
649
|
+
return {
|
|
650
|
+
blocks: [
|
|
651
|
+
{ type: "header", text: `Document SEO \u2014 ${route}` },
|
|
652
|
+
{
|
|
653
|
+
type: "context",
|
|
654
|
+
text: findings.length === 0 ? `No findings for ${route} \u2014 the document is currently clean.` : `${findings.length} findings \u2014 ${errors.length} errors, ${warnings.length} warnings.`
|
|
655
|
+
},
|
|
656
|
+
...findings.length === 0 ? [] : [{ type: "divider" }, documentFindingsTable(sorted)]
|
|
657
|
+
]
|
|
658
|
+
};
|
|
659
|
+
}
|
|
660
|
+
function documentFindingsTable(findings) {
|
|
661
|
+
return {
|
|
662
|
+
type: "table",
|
|
663
|
+
columns: [
|
|
664
|
+
{ key: "rule", label: "Rule", format: "code" },
|
|
665
|
+
{ key: "severity", label: "Severity", format: "badge" },
|
|
666
|
+
{ key: "message", label: "Message" },
|
|
667
|
+
{ key: "block", label: "Block", format: "code" }
|
|
668
|
+
],
|
|
669
|
+
rows: findings.map((finding) => ({
|
|
670
|
+
rule: finding.rule_id,
|
|
671
|
+
severity: finding.severity,
|
|
672
|
+
message: finding.message,
|
|
673
|
+
// The bridge stamps every Portable Text block with id + data-pt-key
|
|
674
|
+
// when rendering; surfacing the path:line locator here lets authors
|
|
675
|
+
// locate the failing block via the editor's ⌘-F.
|
|
676
|
+
block: `${finding.path}:${finding.line}`
|
|
677
|
+
}))
|
|
678
|
+
};
|
|
679
|
+
}
|
|
680
|
+
function notFound(page) {
|
|
681
|
+
return {
|
|
682
|
+
blocks: [
|
|
683
|
+
{ type: "header", text: "Not found" },
|
|
684
|
+
{ type: "context", text: `unknown page: ${page}` }
|
|
685
|
+
]
|
|
686
|
+
};
|
|
687
|
+
}
|
|
688
|
+
|
|
397
689
|
// wasm/aexeo_emdash_bridge_bg.js
|
|
398
690
|
var aexeo_emdash_bridge_bg_exports = {};
|
|
399
691
|
__export(aexeo_emdash_bridge_bg_exports, {
|
|
@@ -1012,11 +1304,11 @@ async function checkWikipedia(input, fetcher) {
|
|
|
1012
1304
|
const descriptions = body[2];
|
|
1013
1305
|
const urls = body[3];
|
|
1014
1306
|
if (!Array.isArray(titles) || !Array.isArray(urls) || titles.length === 0) {
|
|
1015
|
-
return
|
|
1307
|
+
return notFound2("wikipedia");
|
|
1016
1308
|
}
|
|
1017
1309
|
const title = String(titles[0]);
|
|
1018
1310
|
if (!fuzzyMatch(title, input.name)) {
|
|
1019
|
-
return
|
|
1311
|
+
return notFound2("wikipedia");
|
|
1020
1312
|
}
|
|
1021
1313
|
const articleUrl = String(urls[0] ?? "");
|
|
1022
1314
|
const description = Array.isArray(descriptions) ? String(descriptions[0] ?? "") : "";
|
|
@@ -1042,11 +1334,11 @@ async function checkWikidata(input, fetcher) {
|
|
|
1042
1334
|
const body = await res.json();
|
|
1043
1335
|
const top = body.search?.[0];
|
|
1044
1336
|
if (!top || typeof top.id !== "string") {
|
|
1045
|
-
return
|
|
1337
|
+
return notFound2("wikidata");
|
|
1046
1338
|
}
|
|
1047
1339
|
const label = top.label ?? input.name;
|
|
1048
1340
|
if (!fuzzyMatch(label, input.name)) {
|
|
1049
|
-
return
|
|
1341
|
+
return notFound2("wikidata");
|
|
1050
1342
|
}
|
|
1051
1343
|
return found("wikidata", {
|
|
1052
1344
|
label: `${top.id}${top.label ? ` \u2014 ${top.label}` : ""}`,
|
|
@@ -1072,7 +1364,7 @@ async function checkGitHub(input, fetcher) {
|
|
|
1072
1364
|
// it the API can return either v3 or experimental shapes.
|
|
1073
1365
|
headers: { Accept: "application/vnd.github+json" }
|
|
1074
1366
|
});
|
|
1075
|
-
if (res.status === 404) return
|
|
1367
|
+
if (res.status === 404) return notFound2("github");
|
|
1076
1368
|
if (res.status === 403 || res.status === 429) {
|
|
1077
1369
|
return unreachable("github", "rate-limited (try Refresh later)");
|
|
1078
1370
|
}
|
|
@@ -1104,7 +1396,7 @@ async function checkRdap(input, fetcher) {
|
|
|
1104
1396
|
const url = `https://rdap.org/domain/${encodeURIComponent(host)}`;
|
|
1105
1397
|
try {
|
|
1106
1398
|
const res = await timedFetch(fetcher, url);
|
|
1107
|
-
if (res.status === 404) return
|
|
1399
|
+
if (res.status === 404) return notFound2("rdap");
|
|
1108
1400
|
if (!res.ok) return unreachable("rdap", `HTTP ${res.status}`);
|
|
1109
1401
|
const body = await res.json();
|
|
1110
1402
|
const reg = (body.events ?? []).find(
|
|
@@ -1141,13 +1433,13 @@ async function checkCommonCrawl(input, fetcher) {
|
|
|
1141
1433
|
url.searchParams.set("limit", "1");
|
|
1142
1434
|
try {
|
|
1143
1435
|
const res = await timedFetch(fetcher, url.toString());
|
|
1144
|
-
if (res.status === 404) return
|
|
1436
|
+
if (res.status === 404) return notFound2("common_crawl");
|
|
1145
1437
|
if (!res.ok) return unreachable("common_crawl", `HTTP ${res.status}`);
|
|
1146
1438
|
const text = (await res.text()).trim();
|
|
1147
|
-
if (text.length === 0) return
|
|
1439
|
+
if (text.length === 0) return notFound2("common_crawl");
|
|
1148
1440
|
let firstLine = text.split("\n")[0];
|
|
1149
1441
|
if (firstLine === void 0 || firstLine.length === 0) {
|
|
1150
|
-
return
|
|
1442
|
+
return notFound2("common_crawl");
|
|
1151
1443
|
}
|
|
1152
1444
|
let entry;
|
|
1153
1445
|
try {
|
|
@@ -1189,7 +1481,7 @@ function found(source, details) {
|
|
|
1189
1481
|
if (details.extra !== void 0) result.extra = details.extra;
|
|
1190
1482
|
return result;
|
|
1191
1483
|
}
|
|
1192
|
-
function
|
|
1484
|
+
function notFound2(source) {
|
|
1193
1485
|
return { source, status: "not_found", checkedAt: nowIso() };
|
|
1194
1486
|
}
|
|
1195
1487
|
function unreachable(source, error) {
|
|
@@ -1258,7 +1550,7 @@ async function handlePresenceRoute(ctx) {
|
|
|
1258
1550
|
case "data":
|
|
1259
1551
|
return await handleData2(ctx);
|
|
1260
1552
|
case "refresh":
|
|
1261
|
-
return await
|
|
1553
|
+
return await handleRefresh2(ctx);
|
|
1262
1554
|
default:
|
|
1263
1555
|
throw PluginRouteError2.badRequest(
|
|
1264
1556
|
`unknown presence route kind: ${String(body.kind)}`
|
|
@@ -1285,7 +1577,7 @@ async function handleData2(ctx) {
|
|
|
1285
1577
|
const state = ageMs !== null && ageMs > CACHE_TTL_MS ? "stale" : "fresh";
|
|
1286
1578
|
return payload(state, cached.results, cached.generatedAt, entity.name);
|
|
1287
1579
|
}
|
|
1288
|
-
async function
|
|
1580
|
+
async function handleRefresh2(ctx) {
|
|
1289
1581
|
const manifest = await readStoredFacts(ctx.kv);
|
|
1290
1582
|
if (manifest === null) {
|
|
1291
1583
|
return payload("no_manifest", [], null, null);
|
|
@@ -1373,7 +1665,7 @@ function globToMatcher(pattern) {
|
|
|
1373
1665
|
}
|
|
1374
1666
|
|
|
1375
1667
|
// src/version.ts
|
|
1376
|
-
var PACKAGE_VERSION = "0.8.
|
|
1668
|
+
var PACKAGE_VERSION = "0.8.19";
|
|
1377
1669
|
|
|
1378
1670
|
// src/configured.ts
|
|
1379
1671
|
import { definePlugin } from "emdash";
|
|
@@ -1459,7 +1751,7 @@ function createPlugin(options = {}) {
|
|
|
1459
1751
|
// one consumer of `collections`. Bind the resolved list here so
|
|
1460
1752
|
// every request handles the same set without re-reading options.
|
|
1461
1753
|
admin: {
|
|
1462
|
-
handler: (ctx) => handleAdminRoute(ctx, collections)
|
|
1754
|
+
handler: (ctx) => handleAdminRoute(ctx, collections, suppressionFilter)
|
|
1463
1755
|
},
|
|
1464
1756
|
// JSON data endpoint for the React adminEntry. Two routes:
|
|
1465
1757
|
// POST /_emdash/api/plugins/aexeo-emdash/data — read current findings
|
|
@@ -1529,246 +1821,59 @@ function createPlugin(options = {}) {
|
|
|
1529
1821
|
});
|
|
1530
1822
|
}
|
|
1531
1823
|
var configured_default = createPlugin;
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
page: typeof raw.page === "string" && raw.page.length > 0 ? raw.page : "/findings"
|
|
1541
|
-
};
|
|
1542
|
-
}
|
|
1543
|
-
if (raw.type === "block_action") {
|
|
1544
|
-
return {
|
|
1545
|
-
type: "block_action",
|
|
1546
|
-
action_id: typeof raw.action_id === "string" ? raw.action_id : "",
|
|
1547
|
-
...typeof raw.block_id === "string" ? { block_id: raw.block_id } : {},
|
|
1548
|
-
value: raw.value
|
|
1549
|
-
};
|
|
1550
|
-
}
|
|
1551
|
-
if (raw.type === "form_submit") {
|
|
1552
|
-
return {
|
|
1553
|
-
type: "form_submit",
|
|
1554
|
-
action_id: typeof raw.action_id === "string" ? raw.action_id : "",
|
|
1555
|
-
...typeof raw.block_id === "string" ? { block_id: raw.block_id } : {},
|
|
1556
|
-
values: raw.values && typeof raw.values === "object" ? raw.values : {}
|
|
1557
|
-
};
|
|
1558
|
-
}
|
|
1559
|
-
return { type: "page_load", page: "/findings" };
|
|
1560
|
-
}
|
|
1561
|
-
async function handleAdminRoute(ctx, collections) {
|
|
1824
|
+
var adminViewTuning = {
|
|
1825
|
+
emptyFindingsText: "Once a Refresh runs, findings will list here.",
|
|
1826
|
+
emptyScoreText: "No documents indexed \u2014 click Refresh on the Aexeo findings page.",
|
|
1827
|
+
refreshFailureVariant: "error",
|
|
1828
|
+
score: async (documents, kv) => scoreLocally(documents, await readStoredFacts(kv)),
|
|
1829
|
+
truthLabel: formatTruthLabel
|
|
1830
|
+
};
|
|
1831
|
+
async function handleAdminRoute(ctx, collections, suppressionFilter) {
|
|
1562
1832
|
const body = normalizeInteraction(ctx.input);
|
|
1563
1833
|
if (body.type === "page_load") {
|
|
1564
1834
|
return handlePageLoad(ctx, body.page);
|
|
1565
1835
|
}
|
|
1566
1836
|
if (body.type === "block_action") {
|
|
1567
1837
|
if (body.action_id === "view_document" && typeof body.value === "string") {
|
|
1568
|
-
return renderDocumentPanel(ctx, body.value);
|
|
1838
|
+
return renderDocumentPanel(ctx.kv, body.value);
|
|
1569
1839
|
}
|
|
1570
1840
|
if (body.action_id === "refresh_findings") {
|
|
1571
|
-
return
|
|
1841
|
+
return handleRefresh({
|
|
1842
|
+
ctx,
|
|
1843
|
+
collections,
|
|
1844
|
+
evaluator: inProcessEvaluator,
|
|
1845
|
+
suppressionFilter,
|
|
1846
|
+
tuning: adminViewTuning
|
|
1847
|
+
});
|
|
1572
1848
|
}
|
|
1573
1849
|
if (body.action_id.startsWith("filter:")) {
|
|
1574
|
-
return renderFindingsPage(ctx);
|
|
1850
|
+
return renderFindingsPage(ctx.kv, adminViewTuning);
|
|
1575
1851
|
}
|
|
1576
|
-
return
|
|
1852
|
+
return notFound(body.action_id);
|
|
1577
1853
|
}
|
|
1578
1854
|
if (body.type === "form_submit") {
|
|
1579
1855
|
if (body.action_id === "view_document") {
|
|
1580
1856
|
const picked = body.values["route_picker"];
|
|
1581
1857
|
if (typeof picked === "string" && picked.length > 0) {
|
|
1582
|
-
return renderDocumentPanel(ctx, picked);
|
|
1858
|
+
return renderDocumentPanel(ctx.kv, picked);
|
|
1583
1859
|
}
|
|
1584
1860
|
}
|
|
1585
|
-
return renderFindingsPage(ctx);
|
|
1861
|
+
return renderFindingsPage(ctx.kv, adminViewTuning);
|
|
1586
1862
|
}
|
|
1587
|
-
return renderFindingsPage(ctx);
|
|
1863
|
+
return renderFindingsPage(ctx.kv, adminViewTuning);
|
|
1588
1864
|
}
|
|
1589
1865
|
async function handlePageLoad(ctx, page) {
|
|
1590
1866
|
const normalized = page.startsWith("/") ? page.slice(1) : page;
|
|
1591
1867
|
if (normalized === "" || normalized === "findings") {
|
|
1592
|
-
return renderFindingsPage(ctx);
|
|
1868
|
+
return renderFindingsPage(ctx.kv, adminViewTuning);
|
|
1593
1869
|
}
|
|
1594
1870
|
if (normalized === "widget:aexeo-score") {
|
|
1595
|
-
return renderScoreWidget(ctx);
|
|
1871
|
+
return renderScoreWidget(ctx.kv, adminViewTuning);
|
|
1596
1872
|
}
|
|
1597
1873
|
if (normalized === "document") {
|
|
1598
|
-
return renderDocumentPanel(ctx);
|
|
1874
|
+
return renderDocumentPanel(ctx.kv);
|
|
1599
1875
|
}
|
|
1600
|
-
return
|
|
1601
|
-
}
|
|
1602
|
-
async function handleRefresh2(ctx, collections) {
|
|
1603
|
-
let summary;
|
|
1604
|
-
try {
|
|
1605
|
-
summary = await evaluateAndPersistAll(ctx, {
|
|
1606
|
-
evaluator: inProcessEvaluator,
|
|
1607
|
-
collections
|
|
1608
|
-
});
|
|
1609
|
-
} catch (err) {
|
|
1610
|
-
const detail = err instanceof Error ? err.message : String(err);
|
|
1611
|
-
return {
|
|
1612
|
-
blocks: [
|
|
1613
|
-
{ type: "header", text: "SEO findings" },
|
|
1614
|
-
{
|
|
1615
|
-
type: "banner",
|
|
1616
|
-
title: `Refresh failed: ${detail}`,
|
|
1617
|
-
variant: "error"
|
|
1618
|
-
}
|
|
1619
|
-
],
|
|
1620
|
-
toast: { message: `Refresh failed: ${detail}`, type: "error" }
|
|
1621
|
-
};
|
|
1622
|
-
}
|
|
1623
|
-
const refreshed = await renderFindingsPage(ctx);
|
|
1624
|
-
const toastMessage = summary.errors.length === 0 ? `Refreshed ${summary.routesUpdated} routes (${summary.totalFindings} findings across ${summary.documentsScanned} documents)` : `Refresh completed with ${summary.errors.length} errors \u2014 see banner`;
|
|
1625
|
-
if (summary.errors.length > 0) {
|
|
1626
|
-
refreshed.blocks.unshift({
|
|
1627
|
-
type: "banner",
|
|
1628
|
-
title: `Refresh issues: ${summary.errors.join(" \u2022 ")}`,
|
|
1629
|
-
variant: "alert"
|
|
1630
|
-
});
|
|
1631
|
-
}
|
|
1632
|
-
return {
|
|
1633
|
-
...refreshed,
|
|
1634
|
-
toast: {
|
|
1635
|
-
message: toastMessage,
|
|
1636
|
-
type: summary.errors.length === 0 ? "success" : "info"
|
|
1637
|
-
}
|
|
1638
|
-
};
|
|
1639
|
-
}
|
|
1640
|
-
async function readAllFindings(ctx) {
|
|
1641
|
-
const entries = await ctx.kv.list(
|
|
1642
|
-
"findings:"
|
|
1643
|
-
);
|
|
1644
|
-
const out = [];
|
|
1645
|
-
for (const entry of entries) {
|
|
1646
|
-
if (entry.value === null) continue;
|
|
1647
|
-
const route = entry.key.replace(/^findings:/, "");
|
|
1648
|
-
for (const finding of entry.value.findings) {
|
|
1649
|
-
out.push({ ...finding, document_route: route });
|
|
1650
|
-
}
|
|
1651
|
-
}
|
|
1652
|
-
return out;
|
|
1653
|
-
}
|
|
1654
|
-
function severityFirst(a, b) {
|
|
1655
|
-
const rank = (severity) => severity === "error" ? 0 : 1;
|
|
1656
|
-
const diff = rank(a.severity) - rank(b.severity);
|
|
1657
|
-
if (diff !== 0) return diff;
|
|
1658
|
-
return a.rule_id.localeCompare(b.rule_id);
|
|
1659
|
-
}
|
|
1660
|
-
function uniqueRoutes(rows) {
|
|
1661
|
-
const set = /* @__PURE__ */ new Set();
|
|
1662
|
-
for (const row of rows) set.add(row.document_route);
|
|
1663
|
-
return [...set].sort();
|
|
1664
|
-
}
|
|
1665
|
-
async function renderFindingsPage(ctx) {
|
|
1666
|
-
const findings = await readAllFindings(ctx);
|
|
1667
|
-
const errors = findings.filter((f) => f.severity === "error");
|
|
1668
|
-
const warnings = findings.filter((f) => f.severity === "warning");
|
|
1669
|
-
const sorted = [...findings].sort(severityFirst);
|
|
1670
|
-
const routes = uniqueRoutes(findings);
|
|
1671
|
-
const blocks = [
|
|
1672
|
-
{ type: "header", text: "SEO findings" },
|
|
1673
|
-
{
|
|
1674
|
-
type: "context",
|
|
1675
|
-
text: findings.length === 0 ? "No findings yet \u2014 click Refresh to evaluate the site." : `${findings.length} findings across ${routes.length} routes \u2014 ${errors.length} errors, ${warnings.length} warnings.`
|
|
1676
|
-
},
|
|
1677
|
-
{ type: "divider" },
|
|
1678
|
-
{
|
|
1679
|
-
type: "actions",
|
|
1680
|
-
elements: [
|
|
1681
|
-
{
|
|
1682
|
-
type: "button",
|
|
1683
|
-
label: "Refresh",
|
|
1684
|
-
action_id: "refresh_findings",
|
|
1685
|
-
style: "primary"
|
|
1686
|
-
},
|
|
1687
|
-
{ type: "button", label: "All", action_id: "filter:all" },
|
|
1688
|
-
{ type: "button", label: "Errors only", action_id: "filter:errors" },
|
|
1689
|
-
{
|
|
1690
|
-
type: "button",
|
|
1691
|
-
label: "Warnings only",
|
|
1692
|
-
action_id: "filter:warnings"
|
|
1693
|
-
}
|
|
1694
|
-
]
|
|
1695
|
-
},
|
|
1696
|
-
sorted.length === 0 ? {
|
|
1697
|
-
type: "context",
|
|
1698
|
-
text: "Once a Refresh runs, findings will list here."
|
|
1699
|
-
} : findingsTable(sorted)
|
|
1700
|
-
];
|
|
1701
|
-
if (routes.length > 0) {
|
|
1702
|
-
blocks.push({
|
|
1703
|
-
type: "form",
|
|
1704
|
-
fields: [
|
|
1705
|
-
{
|
|
1706
|
-
type: "select",
|
|
1707
|
-
action_id: "route_picker",
|
|
1708
|
-
label: "Document to inspect",
|
|
1709
|
-
options: routes.map((route) => ({ label: route, value: route }))
|
|
1710
|
-
}
|
|
1711
|
-
],
|
|
1712
|
-
submit: { label: "View document SEO", action_id: "view_document" }
|
|
1713
|
-
});
|
|
1714
|
-
}
|
|
1715
|
-
return { blocks };
|
|
1716
|
-
}
|
|
1717
|
-
function findingsTable(rows) {
|
|
1718
|
-
return {
|
|
1719
|
-
type: "table",
|
|
1720
|
-
columns: [
|
|
1721
|
-
{ key: "route", label: "Route" },
|
|
1722
|
-
{ key: "rule", label: "Rule", format: "code" },
|
|
1723
|
-
{ key: "severity", label: "Severity", format: "badge" },
|
|
1724
|
-
{ key: "message", label: "Message" }
|
|
1725
|
-
],
|
|
1726
|
-
rows: rows.map((row) => ({
|
|
1727
|
-
route: row.document_route,
|
|
1728
|
-
rule: row.rule_id,
|
|
1729
|
-
severity: row.severity,
|
|
1730
|
-
message: row.message
|
|
1731
|
-
}))
|
|
1732
|
-
};
|
|
1733
|
-
}
|
|
1734
|
-
async function renderScoreWidget(ctx) {
|
|
1735
|
-
const documents = await readAllDocuments(ctx.kv);
|
|
1736
|
-
if (documents.length === 0) {
|
|
1737
|
-
return {
|
|
1738
|
-
blocks: [
|
|
1739
|
-
{ type: "header", text: "SEO score" },
|
|
1740
|
-
{
|
|
1741
|
-
type: "context",
|
|
1742
|
-
text: "No documents indexed \u2014 click Refresh on the Aexeo findings page."
|
|
1743
|
-
}
|
|
1744
|
-
]
|
|
1745
|
-
};
|
|
1746
|
-
}
|
|
1747
|
-
const facts = await readStoredFacts(ctx.kv);
|
|
1748
|
-
const score = await scoreLocally(documents, facts);
|
|
1749
|
-
const truthLabel = formatTruthLabel(score);
|
|
1750
|
-
const blocks = [
|
|
1751
|
-
{
|
|
1752
|
-
type: "stats",
|
|
1753
|
-
items: [
|
|
1754
|
-
{ label: "Overall", value: `${score.overall_score}` },
|
|
1755
|
-
{ label: "Citation", value: `${score.citation_readiness_score}` },
|
|
1756
|
-
{ label: truthLabel, value: `${score.truth_consistency_score}` },
|
|
1757
|
-
{ label: "Answers", value: `${score.answer_pack_score}` }
|
|
1758
|
-
]
|
|
1759
|
-
}
|
|
1760
|
-
];
|
|
1761
|
-
if (score.overall_score < 60) {
|
|
1762
|
-
blocks.unshift({
|
|
1763
|
-
type: "banner",
|
|
1764
|
-
title: `Site score is ${score.overall_score} \u2014 below the 60 quality threshold`,
|
|
1765
|
-
variant: "alert"
|
|
1766
|
-
});
|
|
1767
|
-
}
|
|
1768
|
-
if (score.blockers.length > 0) {
|
|
1769
|
-
blocks.push({ type: "context", text: topBlockersLine(score) });
|
|
1770
|
-
}
|
|
1771
|
-
return { blocks };
|
|
1876
|
+
return notFound(page);
|
|
1772
1877
|
}
|
|
1773
1878
|
async function scoreLocally(documents, manifest = null) {
|
|
1774
1879
|
const manifestJson = manifest === null ? null : JSON.stringify(manifest);
|
|
@@ -1789,63 +1894,6 @@ function formatTruthLabel(score) {
|
|
|
1789
1894
|
return "Truth";
|
|
1790
1895
|
}
|
|
1791
1896
|
}
|
|
1792
|
-
function topBlockersLine(score) {
|
|
1793
|
-
const top = score.blockers.slice(0, 3).map((b) => b.message);
|
|
1794
|
-
if (top.length === 0) return "No blockers identified.";
|
|
1795
|
-
return `Top blockers: ${top.join(" \u2022 ")}`;
|
|
1796
|
-
}
|
|
1797
|
-
async function renderDocumentPanel(ctx, route) {
|
|
1798
|
-
if (route === void 0) {
|
|
1799
|
-
return {
|
|
1800
|
-
blocks: [
|
|
1801
|
-
{ type: "header", text: "Document SEO" },
|
|
1802
|
-
{
|
|
1803
|
-
type: "context",
|
|
1804
|
-
text: "Pick a document from the SEO findings list to see its rule findings here."
|
|
1805
|
-
}
|
|
1806
|
-
]
|
|
1807
|
-
};
|
|
1808
|
-
}
|
|
1809
|
-
const findings = await readFindings(ctx.kv, route);
|
|
1810
|
-
const errors = findings.filter((f) => f.severity === "error");
|
|
1811
|
-
const warnings = findings.filter((f) => f.severity === "warning");
|
|
1812
|
-
const sorted = [...findings].sort(severityFirst);
|
|
1813
|
-
return {
|
|
1814
|
-
blocks: [
|
|
1815
|
-
{ type: "header", text: `Document SEO \u2014 ${route}` },
|
|
1816
|
-
{
|
|
1817
|
-
type: "context",
|
|
1818
|
-
text: findings.length === 0 ? `No findings for ${route} \u2014 the document is currently clean.` : `${findings.length} findings \u2014 ${errors.length} errors, ${warnings.length} warnings.`
|
|
1819
|
-
},
|
|
1820
|
-
...findings.length === 0 ? [] : [{ type: "divider" }, documentFindingsTable(sorted)]
|
|
1821
|
-
]
|
|
1822
|
-
};
|
|
1823
|
-
}
|
|
1824
|
-
function documentFindingsTable(findings) {
|
|
1825
|
-
return {
|
|
1826
|
-
type: "table",
|
|
1827
|
-
columns: [
|
|
1828
|
-
{ key: "rule", label: "Rule", format: "code" },
|
|
1829
|
-
{ key: "severity", label: "Severity", format: "badge" },
|
|
1830
|
-
{ key: "message", label: "Message" },
|
|
1831
|
-
{ key: "block", label: "Block", format: "code" }
|
|
1832
|
-
],
|
|
1833
|
-
rows: findings.map((finding) => ({
|
|
1834
|
-
rule: finding.rule_id,
|
|
1835
|
-
severity: finding.severity,
|
|
1836
|
-
message: finding.message,
|
|
1837
|
-
block: `${finding.path}:${finding.line}`
|
|
1838
|
-
}))
|
|
1839
|
-
};
|
|
1840
|
-
}
|
|
1841
|
-
function notFound2(page) {
|
|
1842
|
-
return {
|
|
1843
|
-
blocks: [
|
|
1844
|
-
{ type: "header", text: "Not found" },
|
|
1845
|
-
{ type: "context", text: `unknown page: ${page}` }
|
|
1846
|
-
]
|
|
1847
|
-
};
|
|
1848
|
-
}
|
|
1849
1897
|
export {
|
|
1850
1898
|
createPlugin,
|
|
1851
1899
|
configured_default as default
|