@farming-labs/docs 0.1.92 → 0.1.93
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/dist/cli/index.mjs
CHANGED
|
@@ -100,7 +100,7 @@ async function main() {
|
|
|
100
100
|
printAgentCompactHelp();
|
|
101
101
|
process.exit(1);
|
|
102
102
|
} else if (parsedCommand.command === "doctor") {
|
|
103
|
-
const { parseDoctorArgs, printDoctorHelp, runDoctor } = await import("../doctor-
|
|
103
|
+
const { parseDoctorArgs, printDoctorHelp, runDoctor } = await import("../doctor-DjLUv0Dq.mjs");
|
|
104
104
|
const doctorOptions = parseDoctorArgs(args.slice(1));
|
|
105
105
|
if (doctorOptions.help) {
|
|
106
106
|
printDoctorHelp();
|
|
@@ -736,7 +736,8 @@ async function probeTextRoute(baseUrl, route) {
|
|
|
736
736
|
return {
|
|
737
737
|
ok: true,
|
|
738
738
|
status: response.status,
|
|
739
|
-
detail: `${route} returned HTTP ${response.status} with ${body.length} characters
|
|
739
|
+
detail: `${route} returned HTTP ${response.status} with ${body.length} characters.`,
|
|
740
|
+
body
|
|
740
741
|
};
|
|
741
742
|
} catch (error) {
|
|
742
743
|
return {
|
|
@@ -745,6 +746,56 @@ async function probeTextRoute(baseUrl, route) {
|
|
|
745
746
|
};
|
|
746
747
|
}
|
|
747
748
|
}
|
|
749
|
+
function decodeHtmlEntity(value) {
|
|
750
|
+
const named = {
|
|
751
|
+
amp: "&",
|
|
752
|
+
apos: "'",
|
|
753
|
+
gt: ">",
|
|
754
|
+
lt: "<",
|
|
755
|
+
quot: "\""
|
|
756
|
+
};
|
|
757
|
+
return value.replace(/&(#x[\da-f]+|#\d+|[a-z]+);/gi, (entity, raw) => {
|
|
758
|
+
const lower = raw.toLowerCase();
|
|
759
|
+
if (lower.startsWith("#x")) {
|
|
760
|
+
const codePoint = Number.parseInt(lower.slice(2), 16);
|
|
761
|
+
return Number.isFinite(codePoint) && codePoint >= 0 && codePoint <= 1114111 ? String.fromCodePoint(codePoint) : entity;
|
|
762
|
+
}
|
|
763
|
+
if (lower.startsWith("#")) {
|
|
764
|
+
const codePoint = Number.parseInt(lower.slice(1), 10);
|
|
765
|
+
return Number.isFinite(codePoint) && codePoint >= 0 && codePoint <= 1114111 ? String.fromCodePoint(codePoint) : entity;
|
|
766
|
+
}
|
|
767
|
+
return named[lower] ?? entity;
|
|
768
|
+
});
|
|
769
|
+
}
|
|
770
|
+
function htmlAttribute(tag, name) {
|
|
771
|
+
for (const match of tag.matchAll(/([^\s"'<>/=]+)\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s"'=<>`]+))/g)) {
|
|
772
|
+
if (match[1]?.toLowerCase() !== name.toLowerCase()) continue;
|
|
773
|
+
return decodeHtmlEntity(match[2] ?? match[3] ?? match[4] ?? "");
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
function hasJsonLdScript(html) {
|
|
777
|
+
return /<script\b(?=[^>]*\btype\s*=\s*["']application\/ld\+json["'])[^>]*>/i.test(html);
|
|
778
|
+
}
|
|
779
|
+
function markdownAlternateHref(html) {
|
|
780
|
+
for (const match of html.matchAll(/<link\b[^>]*>/gi)) {
|
|
781
|
+
const tag = match[0];
|
|
782
|
+
const rel = htmlAttribute(tag, "rel") ?? "";
|
|
783
|
+
const type = htmlAttribute(tag, "type") ?? "";
|
|
784
|
+
const href = htmlAttribute(tag, "href");
|
|
785
|
+
const relTokens = rel.toLowerCase().split(/\s+/).filter(Boolean);
|
|
786
|
+
if (href && relTokens.includes("alternate") && /^text\/markdown(?:\s*;|$)/i.test(type.trim())) return href;
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
function resolveMarkdownAlternateUrl(href, pageUrl) {
|
|
790
|
+
if (!href) return void 0;
|
|
791
|
+
try {
|
|
792
|
+
const url = new URL(href, pageUrl);
|
|
793
|
+
const page = new URL(pageUrl);
|
|
794
|
+
return url.origin === page.origin && url.pathname.endsWith(".md") ? url : void 0;
|
|
795
|
+
} catch {
|
|
796
|
+
return;
|
|
797
|
+
}
|
|
798
|
+
}
|
|
748
799
|
async function probeRobotsRoute(baseUrl, route = DEFAULT_ROBOTS_TXT_ROUTE) {
|
|
749
800
|
const url = joinDoctorUrl(baseUrl, route);
|
|
750
801
|
try {
|
|
@@ -923,6 +974,93 @@ function hostedRobotsRoute(discoveryBody) {
|
|
|
923
974
|
route: readDiscoveryRoute(robots?.route) ?? DEFAULT_ROBOTS_TXT_ROUTE
|
|
924
975
|
};
|
|
925
976
|
}
|
|
977
|
+
function hostedCapability(discoveryBody, key) {
|
|
978
|
+
const root = asRecord(discoveryBody);
|
|
979
|
+
const capability = asRecord(root?.capabilities)?.[key];
|
|
980
|
+
if (typeof capability === "boolean") return capability;
|
|
981
|
+
const enabled = asRecord(root?.[key])?.enabled;
|
|
982
|
+
return typeof enabled === "boolean" ? enabled : void 0;
|
|
983
|
+
}
|
|
984
|
+
function hostedRootDocsRoute(discoveryBody) {
|
|
985
|
+
const site = asRecord(asRecord(discoveryBody)?.site);
|
|
986
|
+
return `/${(typeof site?.entry === "string" && site.entry.trim() ? site.entry.trim() : "docs").replace(/^\/+|\/+$/g, "") || "docs"}`;
|
|
987
|
+
}
|
|
988
|
+
function hostedPageUrl(baseUrl, pageRoute) {
|
|
989
|
+
try {
|
|
990
|
+
const base = new URL(baseUrl);
|
|
991
|
+
const parsed = new URL(pageRoute, base.origin);
|
|
992
|
+
if (parsed.origin !== base.origin) return void 0;
|
|
993
|
+
parsed.hash = "";
|
|
994
|
+
parsed.search = "";
|
|
995
|
+
const basePath = base.pathname.replace(/\/+$/, "");
|
|
996
|
+
const pagePath = parsed.pathname.replace(/\/+$/, "") || "/";
|
|
997
|
+
const pathname = !basePath || pagePath === basePath || pagePath.startsWith(`${basePath}/`) ? pagePath : `${basePath}${pagePath}`;
|
|
998
|
+
return new URL(pathname || "/", base.origin).toString().replace(/\/+$/, "");
|
|
999
|
+
} catch {
|
|
1000
|
+
return;
|
|
1001
|
+
}
|
|
1002
|
+
}
|
|
1003
|
+
function sampleHostedPageUrls(baseUrl, discoveryBody, pages, limit = 10) {
|
|
1004
|
+
const pageRoutes = pages.map((page) => page.url).filter((route) => route.startsWith("/") && !route.endsWith(".md"));
|
|
1005
|
+
const fallback = hostedRootDocsRoute(discoveryBody);
|
|
1006
|
+
const unique = Array.from(new Set(pageRoutes.length > 0 ? pageRoutes : [fallback])).sort();
|
|
1007
|
+
return (unique.length <= limit ? unique : Array.from({ length: limit }, (_, index) => unique[Math.floor(index * (unique.length / limit))])).map((route) => hostedPageUrl(baseUrl, route)).filter((url) => typeof url === "string");
|
|
1008
|
+
}
|
|
1009
|
+
async function probeHostedHtmlPage(url) {
|
|
1010
|
+
try {
|
|
1011
|
+
const response = await fetchWithTimeout(url, { headers: { Accept: "text/html, */*" } });
|
|
1012
|
+
const body = await response.text().catch(() => "");
|
|
1013
|
+
const pathname = new URL(url).pathname;
|
|
1014
|
+
if (!response.ok) return {
|
|
1015
|
+
ok: false,
|
|
1016
|
+
detail: `${pathname} returned HTTP ${response.status}.`,
|
|
1017
|
+
hasJsonLd: false,
|
|
1018
|
+
hasMarkdownAlternate: false
|
|
1019
|
+
};
|
|
1020
|
+
const alternateUrl = resolveMarkdownAlternateUrl(markdownAlternateHref(body), url);
|
|
1021
|
+
return {
|
|
1022
|
+
ok: true,
|
|
1023
|
+
detail: `${pathname} returned HTML with ${body.length} characters.`,
|
|
1024
|
+
hasJsonLd: hasJsonLdScript(body),
|
|
1025
|
+
hasMarkdownAlternate: Boolean(alternateUrl)
|
|
1026
|
+
};
|
|
1027
|
+
} catch (error) {
|
|
1028
|
+
return {
|
|
1029
|
+
ok: false,
|
|
1030
|
+
detail: `${url} failed: ${error instanceof Error ? error.message : String(error)}.`,
|
|
1031
|
+
hasJsonLd: false,
|
|
1032
|
+
hasMarkdownAlternate: false
|
|
1033
|
+
};
|
|
1034
|
+
}
|
|
1035
|
+
}
|
|
1036
|
+
function hostedSurfaceScore(probes, predicate) {
|
|
1037
|
+
const total = probes.length;
|
|
1038
|
+
const passed = probes.filter((probe) => probe.ok && predicate(probe)).length;
|
|
1039
|
+
if (total === 0) return {
|
|
1040
|
+
status: "warn",
|
|
1041
|
+
score: 0,
|
|
1042
|
+
passed: 0,
|
|
1043
|
+
total
|
|
1044
|
+
};
|
|
1045
|
+
if (passed === total) return {
|
|
1046
|
+
status: "pass",
|
|
1047
|
+
score: 5,
|
|
1048
|
+
passed,
|
|
1049
|
+
total
|
|
1050
|
+
};
|
|
1051
|
+
if (passed > 0) return {
|
|
1052
|
+
status: "warn",
|
|
1053
|
+
score: Math.round(passed / total * 5),
|
|
1054
|
+
passed,
|
|
1055
|
+
total
|
|
1056
|
+
};
|
|
1057
|
+
return {
|
|
1058
|
+
status: "fail",
|
|
1059
|
+
score: 0,
|
|
1060
|
+
passed,
|
|
1061
|
+
total
|
|
1062
|
+
};
|
|
1063
|
+
}
|
|
926
1064
|
async function buildHostedAgentChecks(url, pages) {
|
|
927
1065
|
let baseUrl;
|
|
928
1066
|
try {
|
|
@@ -959,6 +1097,14 @@ async function buildHostedAgentChecks(url, pages) {
|
|
|
959
1097
|
const markdown = await probeTextRoute(baseUrl, markdownRoute);
|
|
960
1098
|
checks.push(makeCheck("hosted-markdown", "Hosted markdown route", markdown.ok ? "pass" : "fail", markdown.ok ? 5 : 0, 5, markdown.detail, markdown.ok ? void 0 : `Verify deployed markdown routes are forwarded, starting with ${markdownRoute}.`));
|
|
961
1099
|
} else checks.push(makeCheck("hosted-markdown", "Hosted markdown route", "warn", 0, 5, "No local docs page was available to choose a sample .md route.", "Add docs pages so the hosted doctor can probe a representative .md route."));
|
|
1100
|
+
const htmlPageUrls = sampleHostedPageUrls(baseUrl, discovery.body, pages);
|
|
1101
|
+
const htmlPageProbes = await Promise.all(htmlPageUrls.map((pageUrl) => probeHostedHtmlPage(pageUrl)));
|
|
1102
|
+
const structuredDataScore = hostedSurfaceScore(htmlPageProbes, (probe) => probe.hasJsonLd);
|
|
1103
|
+
const structuredDataEnabled = hostedCapability(discovery.body, "structuredData");
|
|
1104
|
+
checks.push(makeCheck("hosted-structured-data", "Hosted structured data", structuredDataEnabled === false ? "warn" : structuredDataScore.status, structuredDataEnabled === false ? 0 : structuredDataScore.score, 5, structuredDataEnabled === false ? "The hosted discovery spec reports structured data as disabled." : structuredDataScore.total > 0 ? `${structuredDataScore.passed}/${structuredDataScore.total} sampled hosted docs pages include application/ld+json structured data.` : "No hosted docs pages were available to verify application/ld+json structured data.", structuredDataEnabled === false || structuredDataScore.status === "pass" ? void 0 : "Keep JSON-LD enabled on every docs page so agents can read canonical title, description, URL, breadcrumbs, and freshness hints."));
|
|
1105
|
+
const markdownAlternateScore = hostedSurfaceScore(htmlPageProbes, (probe) => probe.hasMarkdownAlternate);
|
|
1106
|
+
const markdownRoutesEnabled = hostedCapability(discovery.body, "markdownRoutes");
|
|
1107
|
+
checks.push(makeCheck("hosted-markdown-alternate", "Hosted markdown alternate links", markdownRoutesEnabled === false ? "warn" : markdownAlternateScore.status, markdownRoutesEnabled === false ? 0 : markdownAlternateScore.score, 5, markdownRoutesEnabled === false ? "The hosted discovery spec reports markdown routes as disabled." : markdownAlternateScore.total > 0 ? `${markdownAlternateScore.passed}/${markdownAlternateScore.total} sampled hosted docs pages include <link rel="alternate" type="text/markdown"> pointing to .md routes.` : "No hosted docs pages were available to verify markdown alternate links.", markdownRoutesEnabled === false || markdownAlternateScore.status === "pass" ? void 0 : "Add a text/markdown alternate link in each docs page head, usually through `alternates.types['text/markdown']`, so agents can discover the page markdown URL from HTML."));
|
|
962
1108
|
const mcp = await Promise.all([probeMcpRoute(baseUrl, DEFAULT_MCP_PUBLIC_ROUTE), probeMcpRoute(baseUrl, DEFAULT_MCP_WELL_KNOWN_ROUTE)]);
|
|
963
1109
|
const mcpPassed = mcp.filter((result) => result.ok).length;
|
|
964
1110
|
checks.push(makeCheck("hosted-mcp", "Hosted MCP handshake", mcpPassed === mcp.length ? "pass" : mcpPassed > 0 ? "warn" : "fail", mcpPassed === mcp.length ? 10 : mcpPassed > 0 ? 5 : 0, 10, mcp.map((result) => result.detail).join(" "), mcpPassed === mcp.length ? void 0 : `Verify deployed ${DEFAULT_MCP_PUBLIC_ROUTE} and ${DEFAULT_MCP_WELL_KNOWN_ROUTE} support Streamable HTTP initialize and tools/list.`));
|