agentimization 0.1.0 → 0.1.2
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 +39 -155
- package/dist/index.js +531 -171
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -9,7 +9,6 @@ var __export = (target, all) => {
|
|
|
9
9
|
import { Command } from "commander";
|
|
10
10
|
import { render } from "ink";
|
|
11
11
|
import React2 from "react";
|
|
12
|
-
import { resolve as resolve3 } from "path";
|
|
13
12
|
|
|
14
13
|
// ../../node_modules/zod/v3/external.js
|
|
15
14
|
var external_exports = {};
|
|
@@ -4052,7 +4051,9 @@ var coerce = {
|
|
|
4052
4051
|
};
|
|
4053
4052
|
var NEVER = INVALID;
|
|
4054
4053
|
|
|
4055
|
-
// node_modules/@agentimization/core/
|
|
4054
|
+
// node_modules/@agentimization/core/dist/index.js
|
|
4055
|
+
import { readFileSync, readdirSync, existsSync } from "fs";
|
|
4056
|
+
import { dirname, join, relative, extname } from "path";
|
|
4056
4057
|
var CHECK_STATUSES = ["pass", "warn", "fail", "skip", "info"];
|
|
4057
4058
|
var CHECK_CATEGORIES = [
|
|
4058
4059
|
"content-discoverability",
|
|
@@ -4104,8 +4105,6 @@ var DEFAULT_CONFIG = {
|
|
|
4104
4105
|
onEvent: () => {
|
|
4105
4106
|
}
|
|
4106
4107
|
};
|
|
4107
|
-
|
|
4108
|
-
// node_modules/@agentimization/core/dist/checks/content-discoverability.js
|
|
4109
4108
|
var llmsTxtExists = {
|
|
4110
4109
|
id: "llms-txt-exists",
|
|
4111
4110
|
name: "llms.txt Exists",
|
|
@@ -4151,17 +4150,13 @@ var llmsTxtValid = {
|
|
|
4151
4150
|
const issues = [];
|
|
4152
4151
|
const lines = ctx.llmsTxt.split("\n");
|
|
4153
4152
|
const hasH1 = lines.some((l) => /^#\s+/.test(l));
|
|
4154
|
-
if (!hasH1)
|
|
4155
|
-
issues.push("Missing H1 title");
|
|
4153
|
+
if (!hasH1) issues.push("Missing H1 title");
|
|
4156
4154
|
const hasBlockquote = lines.some((l) => /^>\s+/.test(l));
|
|
4157
|
-
if (!hasBlockquote)
|
|
4158
|
-
issues.push("Missing blockquote description");
|
|
4155
|
+
if (!hasBlockquote) issues.push("Missing blockquote description");
|
|
4159
4156
|
const hasHeadingSections = lines.some((l) => /^##\s+/.test(l));
|
|
4160
|
-
if (!hasHeadingSections)
|
|
4161
|
-
issues.push("Missing ## section headings");
|
|
4157
|
+
if (!hasHeadingSections) issues.push("Missing ## section headings");
|
|
4162
4158
|
const hasLinks = /\[.+\]\(.+\)/.test(ctx.llmsTxt);
|
|
4163
|
-
if (!hasLinks)
|
|
4164
|
-
issues.push("No markdown links found");
|
|
4159
|
+
if (!hasLinks) issues.push("No markdown links found");
|
|
4165
4160
|
if (issues.length === 0) {
|
|
4166
4161
|
return {
|
|
4167
4162
|
id: "llms-txt-valid",
|
|
@@ -4245,8 +4240,7 @@ var llmsTxtFreshness = {
|
|
|
4245
4240
|
};
|
|
4246
4241
|
}
|
|
4247
4242
|
const sharedRegistrable = (a, b) => {
|
|
4248
|
-
if (a === b)
|
|
4249
|
-
return true;
|
|
4243
|
+
if (a === b) return true;
|
|
4250
4244
|
const aParts = a.split(".");
|
|
4251
4245
|
const bParts = b.split(".");
|
|
4252
4246
|
const tail = (parts) => parts.slice(-2).join(".");
|
|
@@ -4255,8 +4249,7 @@ var llmsTxtFreshness = {
|
|
|
4255
4249
|
const keyFor = (raw) => {
|
|
4256
4250
|
try {
|
|
4257
4251
|
const u = new URL(raw, ctx.baseUrl.origin);
|
|
4258
|
-
if (!sharedRegistrable(u.hostname, ctx.baseUrl.hostname))
|
|
4259
|
-
return null;
|
|
4252
|
+
if (!sharedRegistrable(u.hostname, ctx.baseUrl.hostname)) return null;
|
|
4260
4253
|
let path = u.pathname.length > 1 ? u.pathname.replace(/\/+$/, "") : u.pathname;
|
|
4261
4254
|
path = path.replace(/\.(md|mdx|markdown)$/i, "");
|
|
4262
4255
|
return path.toLowerCase();
|
|
@@ -4269,14 +4262,12 @@ var llmsTxtFreshness = {
|
|
|
4269
4262
|
let match;
|
|
4270
4263
|
while ((match = linkRegex.exec(ctx.llmsTxt)) !== null) {
|
|
4271
4264
|
const k = keyFor(match[1]);
|
|
4272
|
-
if (k)
|
|
4273
|
-
llmsKeys.add(k);
|
|
4265
|
+
if (k) llmsKeys.add(k);
|
|
4274
4266
|
}
|
|
4275
4267
|
const sitemapKeys = /* @__PURE__ */ new Set();
|
|
4276
4268
|
for (const u of ctx.sitemapUrls) {
|
|
4277
4269
|
const k = keyFor(u);
|
|
4278
|
-
if (k)
|
|
4279
|
-
sitemapKeys.add(k);
|
|
4270
|
+
if (k) sitemapKeys.add(k);
|
|
4280
4271
|
}
|
|
4281
4272
|
if (sitemapKeys.size === 0 || llmsKeys.size === 0) {
|
|
4282
4273
|
return {
|
|
@@ -4361,11 +4352,15 @@ var llmsTxtLinksResolve = {
|
|
|
4361
4352
|
};
|
|
4362
4353
|
}
|
|
4363
4354
|
const sampled = urls.slice(0, 10);
|
|
4364
|
-
const results = await Promise.allSettled(
|
|
4365
|
-
|
|
4366
|
-
|
|
4367
|
-
|
|
4368
|
-
|
|
4355
|
+
const results = await Promise.allSettled(
|
|
4356
|
+
sampled.map(async (url) => {
|
|
4357
|
+
const resp = await fetch(url, { method: "HEAD", redirect: "follow" });
|
|
4358
|
+
return { url, status: resp.status };
|
|
4359
|
+
})
|
|
4360
|
+
);
|
|
4361
|
+
const resolved = results.filter(
|
|
4362
|
+
(r) => r.status === "fulfilled" && r.value.status >= 200 && r.value.status < 400
|
|
4363
|
+
).length;
|
|
4369
4364
|
if (resolved === sampled.length) {
|
|
4370
4365
|
return {
|
|
4371
4366
|
id: "llms-txt-links-resolve",
|
|
@@ -4527,9 +4522,10 @@ var robotsTxtAgentRules = {
|
|
|
4527
4522
|
currentAgent = agentMatch[1].trim();
|
|
4528
4523
|
continue;
|
|
4529
4524
|
}
|
|
4530
|
-
const matchedAgent = aiAgents.find(
|
|
4531
|
-
|
|
4532
|
-
|
|
4525
|
+
const matchedAgent = aiAgents.find(
|
|
4526
|
+
(a) => currentAgent === a || currentAgent === "*"
|
|
4527
|
+
);
|
|
4528
|
+
if (!matchedAgent) continue;
|
|
4533
4529
|
if (/^Disallow:\s*\/\s*$/i.test(line) && currentAgent !== "*") {
|
|
4534
4530
|
blocked.push(currentAgent);
|
|
4535
4531
|
} else if (/^Allow:\s*\//i.test(line) && currentAgent !== "*") {
|
|
@@ -4567,8 +4563,6 @@ var contentDiscoverabilityChecks = [
|
|
|
4567
4563
|
sitemapExists,
|
|
4568
4564
|
robotsTxtAgentRules
|
|
4569
4565
|
];
|
|
4570
|
-
|
|
4571
|
-
// node_modules/@agentimization/core/dist/utils/fetch.js
|
|
4572
4566
|
var makeHeaders = (config) => ({
|
|
4573
4567
|
"User-Agent": config.userAgent ?? DEFAULT_CONFIG.userAgent,
|
|
4574
4568
|
Accept: "text/html,application/xhtml+xml,text/markdown,text/plain,*/*"
|
|
@@ -4638,7 +4632,9 @@ var fetchMany = async (urls, config = {}) => {
|
|
|
4638
4632
|
const results = [];
|
|
4639
4633
|
for (let i = 0; i < urls.length; i += concurrency) {
|
|
4640
4634
|
const chunk = urls.slice(i, i + concurrency);
|
|
4641
|
-
const chunkResults = await Promise.allSettled(
|
|
4635
|
+
const chunkResults = await Promise.allSettled(
|
|
4636
|
+
chunk.map((url) => fetchPage(url, config))
|
|
4637
|
+
);
|
|
4642
4638
|
for (const result of chunkResults) {
|
|
4643
4639
|
if (result.status === "fulfilled") {
|
|
4644
4640
|
results.push(result.value);
|
|
@@ -4647,8 +4643,6 @@ var fetchMany = async (urls, config = {}) => {
|
|
|
4647
4643
|
}
|
|
4648
4644
|
return results;
|
|
4649
4645
|
};
|
|
4650
|
-
|
|
4651
|
-
// node_modules/@agentimization/core/dist/checks/markdown-availability.js
|
|
4652
4646
|
var markdownUrlSupport = {
|
|
4653
4647
|
id: "markdown-url-support",
|
|
4654
4648
|
name: "Markdown URL Support",
|
|
@@ -4766,8 +4760,7 @@ var markdownContentParity = {
|
|
|
4766
4760
|
let totalMissing = 0;
|
|
4767
4761
|
let checked = 0;
|
|
4768
4762
|
for (const page of pagesWithMarkdown) {
|
|
4769
|
-
if (!page.markdown)
|
|
4770
|
-
continue;
|
|
4763
|
+
if (!page.markdown) continue;
|
|
4771
4764
|
checked++;
|
|
4772
4765
|
const htmlText = page.html.replace(/<script[\s\S]*?<\/script>/gi, "").replace(/<style[\s\S]*?<\/style>/gi, "").replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim();
|
|
4773
4766
|
const htmlWords = new Set(htmlText.toLowerCase().split(/\s+/).filter((w) => w.length > 3));
|
|
@@ -4803,8 +4796,6 @@ var markdownAvailabilityChecks = [
|
|
|
4803
4796
|
contentNegotiation,
|
|
4804
4797
|
markdownContentParity
|
|
4805
4798
|
];
|
|
4806
|
-
|
|
4807
|
-
// node_modules/@agentimization/core/dist/utils/html.js
|
|
4808
4799
|
var stripHtml = (html) => html.replace(/<script[\s\S]*?<\/script>/gi, "").replace(/<style[\s\S]*?<\/style>/gi, "").replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim();
|
|
4809
4800
|
var extractLinks = (html, baseUrl) => {
|
|
4810
4801
|
const links = [];
|
|
@@ -4824,11 +4815,11 @@ var extractMetaTags = (html) => {
|
|
|
4824
4815
|
const metaRegex = /<meta[^>]+(?:name|property)=["']([^"']+)["'][^>]+content=["']([^"']+)["']/gi;
|
|
4825
4816
|
let match;
|
|
4826
4817
|
while ((match = metaRegex.exec(html)) !== null) {
|
|
4827
|
-
meta[match[1]] = match[2];
|
|
4818
|
+
meta[match[1].toLowerCase()] = match[2];
|
|
4828
4819
|
}
|
|
4829
4820
|
const metaRegex2 = /<meta[^>]+content=["']([^"']+)["'][^>]+(?:name|property)=["']([^"']+)["']/gi;
|
|
4830
4821
|
while ((match = metaRegex2.exec(html)) !== null) {
|
|
4831
|
-
meta[match[2]] = match[1];
|
|
4822
|
+
meta[match[2].toLowerCase()] = match[1];
|
|
4832
4823
|
}
|
|
4833
4824
|
return meta;
|
|
4834
4825
|
};
|
|
@@ -4844,6 +4835,24 @@ var extractJsonLd = (html) => {
|
|
|
4844
4835
|
}
|
|
4845
4836
|
return results;
|
|
4846
4837
|
};
|
|
4838
|
+
var readAttr = (attrs, name) => {
|
|
4839
|
+
const re = new RegExp(`\\b${name}=(?:"([^"]*)"|'([^']*)')`, "i");
|
|
4840
|
+
const m = attrs.match(re);
|
|
4841
|
+
if (!m) return void 0;
|
|
4842
|
+
return m[1] ?? m[2];
|
|
4843
|
+
};
|
|
4844
|
+
var extractImages = (html) => {
|
|
4845
|
+
const images = [];
|
|
4846
|
+
const imgRegex = /<img\b([^>]*)>/gi;
|
|
4847
|
+
let match;
|
|
4848
|
+
while ((match = imgRegex.exec(html)) !== null) {
|
|
4849
|
+
const attrs = match[1];
|
|
4850
|
+
const src = readAttr(attrs, "src");
|
|
4851
|
+
if (src === void 0) continue;
|
|
4852
|
+
images.push({ src, alt: readAttr(attrs, "alt") });
|
|
4853
|
+
}
|
|
4854
|
+
return images;
|
|
4855
|
+
};
|
|
4847
4856
|
var extractHeadings = (html) => {
|
|
4848
4857
|
const headings = [];
|
|
4849
4858
|
const regex = /<h([1-6])[^>]*>([\s\S]*?)<\/h\1>/gi;
|
|
@@ -4912,8 +4921,6 @@ var parseSitemapUrls = (xml) => {
|
|
|
4912
4921
|
}
|
|
4913
4922
|
return urls;
|
|
4914
4923
|
};
|
|
4915
|
-
|
|
4916
|
-
// node_modules/@agentimization/core/dist/checks/page-size.js
|
|
4917
4924
|
var MAX_HTML_CHARS = 5e4;
|
|
4918
4925
|
var MAX_MD_CHARS = 5e4;
|
|
4919
4926
|
var renderingStrategy = {
|
|
@@ -4935,8 +4942,7 @@ var renderingStrategy = {
|
|
|
4935
4942
|
}
|
|
4936
4943
|
let ssrCount = 0;
|
|
4937
4944
|
for (const page of pages) {
|
|
4938
|
-
if (hasServerRenderedContent(page.html))
|
|
4939
|
-
ssrCount++;
|
|
4945
|
+
if (hasServerRenderedContent(page.html)) ssrCount++;
|
|
4940
4946
|
}
|
|
4941
4947
|
if (ssrCount === pages.length) {
|
|
4942
4948
|
return {
|
|
@@ -4958,6 +4964,53 @@ var renderingStrategy = {
|
|
|
4958
4964
|
};
|
|
4959
4965
|
}
|
|
4960
4966
|
};
|
|
4967
|
+
var substantialTextContent = {
|
|
4968
|
+
id: "substantial-text-content",
|
|
4969
|
+
name: "Substantial Text Content",
|
|
4970
|
+
category: "page-size",
|
|
4971
|
+
description: "Checks for at least 100 words of readable body text",
|
|
4972
|
+
weight: 0.8,
|
|
4973
|
+
run: async (ctx) => {
|
|
4974
|
+
const pages = ctx.sampledPages.slice(0, 10);
|
|
4975
|
+
if (pages.length === 0) {
|
|
4976
|
+
return {
|
|
4977
|
+
id: "substantial-text-content",
|
|
4978
|
+
name: "Substantial Text Content",
|
|
4979
|
+
category: "page-size",
|
|
4980
|
+
status: "skip",
|
|
4981
|
+
message: "No pages sampled"
|
|
4982
|
+
};
|
|
4983
|
+
}
|
|
4984
|
+
let withSubstantialContent = 0;
|
|
4985
|
+
let totalWords = 0;
|
|
4986
|
+
for (const page of pages) {
|
|
4987
|
+
const text = stripHtml(page.html);
|
|
4988
|
+
const words = text.split(/\s+/).filter((w) => w.length > 0).length;
|
|
4989
|
+
totalWords += words;
|
|
4990
|
+
if (words >= 100) withSubstantialContent++;
|
|
4991
|
+
}
|
|
4992
|
+
const avgWords = Math.round(totalWords / pages.length);
|
|
4993
|
+
if (withSubstantialContent === pages.length) {
|
|
4994
|
+
return {
|
|
4995
|
+
id: "substantial-text-content",
|
|
4996
|
+
name: "Substantial Text Content",
|
|
4997
|
+
category: "page-size",
|
|
4998
|
+
status: "pass",
|
|
4999
|
+
message: `All ${pages.length} pages have \u2265100 words of body text (avg ${avgWords})`,
|
|
5000
|
+
metadata: { withSubstantialContent, avgWords }
|
|
5001
|
+
};
|
|
5002
|
+
}
|
|
5003
|
+
return {
|
|
5004
|
+
id: "substantial-text-content",
|
|
5005
|
+
name: "Substantial Text Content",
|
|
5006
|
+
category: "page-size",
|
|
5007
|
+
status: withSubstantialContent > 0 ? "warn" : "fail",
|
|
5008
|
+
message: `${withSubstantialContent}/${pages.length} pages have \u2265100 words of body text (avg ${avgWords})`,
|
|
5009
|
+
suggestion: "Generative engines can't cite pages that are mostly images or short copy. Add at least 100 words of substantive text content per page.",
|
|
5010
|
+
metadata: { withSubstantialContent, avgWords }
|
|
5011
|
+
};
|
|
5012
|
+
}
|
|
5013
|
+
};
|
|
4961
5014
|
var pageSizeHtml = {
|
|
4962
5015
|
id: "page-size-html",
|
|
4963
5016
|
name: "Page Size (HTML)",
|
|
@@ -5049,7 +5102,7 @@ var contentStartPosition = {
|
|
|
5049
5102
|
id: "content-start-position",
|
|
5050
5103
|
name: "Content Start Position",
|
|
5051
5104
|
category: "page-size",
|
|
5052
|
-
description: "Checks
|
|
5105
|
+
description: "Checks how soon main content starts relative to total HTML",
|
|
5053
5106
|
weight: 0.5,
|
|
5054
5107
|
run: async (ctx) => {
|
|
5055
5108
|
const pages = ctx.sampledPages.slice(0, 10);
|
|
@@ -5066,15 +5119,16 @@ var contentStartPosition = {
|
|
|
5066
5119
|
url: p.url,
|
|
5067
5120
|
position: findContentStartPosition(p.html)
|
|
5068
5121
|
}));
|
|
5069
|
-
const
|
|
5070
|
-
|
|
5071
|
-
|
|
5122
|
+
const medianPct = Math.round(
|
|
5123
|
+
positions.map((p) => p.position).sort((a, b) => a - b)[Math.floor(positions.length / 2)] * 100
|
|
5124
|
+
);
|
|
5125
|
+
if (medianPct <= 30) {
|
|
5072
5126
|
return {
|
|
5073
5127
|
id: "content-start-position",
|
|
5074
5128
|
name: "Content Start Position",
|
|
5075
5129
|
category: "page-size",
|
|
5076
5130
|
status: "pass",
|
|
5077
|
-
message: `
|
|
5131
|
+
message: `content starts at ${medianPct}% of html (median over ${pages.length} pages)`,
|
|
5078
5132
|
metadata: { medianPct }
|
|
5079
5133
|
};
|
|
5080
5134
|
}
|
|
@@ -5082,21 +5136,20 @@ var contentStartPosition = {
|
|
|
5082
5136
|
id: "content-start-position",
|
|
5083
5137
|
name: "Content Start Position",
|
|
5084
5138
|
category: "page-size",
|
|
5085
|
-
status: "warn",
|
|
5086
|
-
message: `
|
|
5087
|
-
suggestion: "
|
|
5088
|
-
metadata: { medianPct
|
|
5139
|
+
status: medianPct <= 50 ? "warn" : "fail",
|
|
5140
|
+
message: `content starts at ${medianPct}% of html (median over ${pages.length} pages)`,
|
|
5141
|
+
suggestion: "trim head metadata or move main content higher in the html so ai agents do not waste context tokens on boilerplate before reaching real content.",
|
|
5142
|
+
metadata: { medianPct }
|
|
5089
5143
|
};
|
|
5090
5144
|
}
|
|
5091
5145
|
};
|
|
5092
5146
|
var pageSizeChecks = [
|
|
5093
5147
|
renderingStrategy,
|
|
5148
|
+
substantialTextContent,
|
|
5094
5149
|
pageSizeHtml,
|
|
5095
5150
|
pageSizeMarkdown,
|
|
5096
5151
|
contentStartPosition
|
|
5097
5152
|
];
|
|
5098
|
-
|
|
5099
|
-
// node_modules/@agentimization/core/dist/checks/content-structure.js
|
|
5100
5153
|
var markdownCodeFenceValidity = {
|
|
5101
5154
|
id: "markdown-code-fence-validity",
|
|
5102
5155
|
name: "Markdown Code Fence Validity",
|
|
@@ -5111,8 +5164,7 @@ var markdownCodeFenceValidity = {
|
|
|
5111
5164
|
for (const page of ctx.sampledPages.slice(0, 10)) {
|
|
5112
5165
|
const codeBlockRegex = /<code[\s\S]*?<\/code>/gi;
|
|
5113
5166
|
const matches = page.html.match(codeBlockRegex);
|
|
5114
|
-
if (matches)
|
|
5115
|
-
totalFences2 += matches.length;
|
|
5167
|
+
if (matches) totalFences2 += matches.length;
|
|
5116
5168
|
}
|
|
5117
5169
|
if (totalFences2 === 0) {
|
|
5118
5170
|
return {
|
|
@@ -5178,8 +5230,7 @@ var sectionHeaderQuality = {
|
|
|
5178
5230
|
const issues = [];
|
|
5179
5231
|
for (const page of pages) {
|
|
5180
5232
|
const headings = extractHeadings(page.html);
|
|
5181
|
-
if (headings.length === 0)
|
|
5182
|
-
continue;
|
|
5233
|
+
if (headings.length === 0) continue;
|
|
5183
5234
|
let pageGood = true;
|
|
5184
5235
|
const hasH1 = headings.some((h) => h.level === 1);
|
|
5185
5236
|
if (!hasH1) {
|
|
@@ -5197,8 +5248,7 @@ var sectionHeaderQuality = {
|
|
|
5197
5248
|
break;
|
|
5198
5249
|
}
|
|
5199
5250
|
}
|
|
5200
|
-
if (pageGood)
|
|
5201
|
-
goodPages++;
|
|
5251
|
+
if (pageGood) goodPages++;
|
|
5202
5252
|
}
|
|
5203
5253
|
if (goodPages === pages.length) {
|
|
5204
5254
|
return {
|
|
@@ -5267,13 +5317,103 @@ var tabbedContentSerialization = {
|
|
|
5267
5317
|
};
|
|
5268
5318
|
}
|
|
5269
5319
|
};
|
|
5320
|
+
var imageAltText = {
|
|
5321
|
+
id: "image-alt-text",
|
|
5322
|
+
name: "Image Alt Text Coverage",
|
|
5323
|
+
category: "content-structure",
|
|
5324
|
+
description: "Checks that at least 50% of images have descriptive alt text",
|
|
5325
|
+
weight: 0.5,
|
|
5326
|
+
run: async (ctx) => {
|
|
5327
|
+
const pages = ctx.sampledPages.slice(0, 10);
|
|
5328
|
+
if (pages.length === 0) {
|
|
5329
|
+
return {
|
|
5330
|
+
id: "image-alt-text",
|
|
5331
|
+
name: "Image Alt Text Coverage",
|
|
5332
|
+
category: "content-structure",
|
|
5333
|
+
status: "skip",
|
|
5334
|
+
message: "No pages sampled"
|
|
5335
|
+
};
|
|
5336
|
+
}
|
|
5337
|
+
const allImages = pages.flatMap((p) => extractImages(p.html));
|
|
5338
|
+
const contentImages = allImages.filter((img) => img.alt === void 0 || img.alt.trim().length > 0);
|
|
5339
|
+
const decorativeImages = allImages.length - contentImages.length;
|
|
5340
|
+
const withAlt = contentImages.filter((img) => img.alt !== void 0 && img.alt.trim().length > 0).length;
|
|
5341
|
+
if (allImages.length === 0) {
|
|
5342
|
+
return {
|
|
5343
|
+
id: "image-alt-text",
|
|
5344
|
+
name: "Image Alt Text Coverage",
|
|
5345
|
+
category: "content-structure",
|
|
5346
|
+
status: "info",
|
|
5347
|
+
message: `No images found across ${pages.length} sampled pages`
|
|
5348
|
+
};
|
|
5349
|
+
}
|
|
5350
|
+
if (contentImages.length === 0) {
|
|
5351
|
+
return {
|
|
5352
|
+
id: "image-alt-text",
|
|
5353
|
+
name: "Image Alt Text Coverage",
|
|
5354
|
+
category: "content-structure",
|
|
5355
|
+
status: "info",
|
|
5356
|
+
message: `All ${allImages.length} sampled images are decorative (alt="")`,
|
|
5357
|
+
metadata: { decorativeImages, totalImages: allImages.length }
|
|
5358
|
+
};
|
|
5359
|
+
}
|
|
5360
|
+
const ratio = withAlt / contentImages.length;
|
|
5361
|
+
const pct = Math.round(ratio * 100);
|
|
5362
|
+
const summary = `${withAlt}/${contentImages.length} content images have descriptive alt text (${pct}%)${decorativeImages > 0 ? `; ${decorativeImages} decorative skipped` : ""}`;
|
|
5363
|
+
if (ratio >= 0.5) {
|
|
5364
|
+
return {
|
|
5365
|
+
id: "image-alt-text",
|
|
5366
|
+
name: "Image Alt Text Coverage",
|
|
5367
|
+
category: "content-structure",
|
|
5368
|
+
status: "pass",
|
|
5369
|
+
message: summary,
|
|
5370
|
+
metadata: { withAlt, contentImages: contentImages.length, decorativeImages, pct }
|
|
5371
|
+
};
|
|
5372
|
+
}
|
|
5373
|
+
return {
|
|
5374
|
+
id: "image-alt-text",
|
|
5375
|
+
name: "Image Alt Text Coverage",
|
|
5376
|
+
category: "content-structure",
|
|
5377
|
+
status: ratio >= 0.25 ? "warn" : "fail",
|
|
5378
|
+
message: summary,
|
|
5379
|
+
suggestion: `Add descriptive alt text to at least 50% of content images. AI agents and screen readers rely on alt text to understand visual content. Mark purely decorative images with alt="" so they don't dilute the ratio.`,
|
|
5380
|
+
metadata: { withAlt, contentImages: contentImages.length, decorativeImages, pct }
|
|
5381
|
+
};
|
|
5382
|
+
}
|
|
5383
|
+
};
|
|
5270
5384
|
var contentStructureChecks = [
|
|
5271
5385
|
markdownCodeFenceValidity,
|
|
5272
5386
|
sectionHeaderQuality,
|
|
5273
|
-
tabbedContentSerialization
|
|
5387
|
+
tabbedContentSerialization,
|
|
5388
|
+
imageAltText
|
|
5274
5389
|
];
|
|
5275
|
-
|
|
5276
|
-
|
|
5390
|
+
var httpsEnabled = {
|
|
5391
|
+
id: "https-enabled",
|
|
5392
|
+
name: "HTTPS Enabled",
|
|
5393
|
+
category: "url-stability",
|
|
5394
|
+
description: "Checks if the site is served over HTTPS",
|
|
5395
|
+
weight: 0.7,
|
|
5396
|
+
requiresNetwork: true,
|
|
5397
|
+
run: async (ctx) => {
|
|
5398
|
+
if (ctx.baseUrl.protocol === "https:") {
|
|
5399
|
+
return {
|
|
5400
|
+
id: "https-enabled",
|
|
5401
|
+
name: "HTTPS Enabled",
|
|
5402
|
+
category: "url-stability",
|
|
5403
|
+
status: "pass",
|
|
5404
|
+
message: "Site is served over HTTPS"
|
|
5405
|
+
};
|
|
5406
|
+
}
|
|
5407
|
+
return {
|
|
5408
|
+
id: "https-enabled",
|
|
5409
|
+
name: "HTTPS Enabled",
|
|
5410
|
+
category: "url-stability",
|
|
5411
|
+
status: "fail",
|
|
5412
|
+
message: `Site is served over ${ctx.baseUrl.protocol.replace(":", "")} \u2014 AI crawlers de-prioritize non-HTTPS sources`,
|
|
5413
|
+
suggestion: "Serve your site over HTTPS. AI crawlers like GPTBot, ClaudeBot, and PerplexityBot strongly prefer HTTPS and may skip plain HTTP entirely."
|
|
5414
|
+
};
|
|
5415
|
+
}
|
|
5416
|
+
};
|
|
5277
5417
|
var httpStatusCodes = {
|
|
5278
5418
|
id: "http-status-codes",
|
|
5279
5419
|
name: "HTTP Status Codes",
|
|
@@ -5290,8 +5430,7 @@ var httpStatusCodes = {
|
|
|
5290
5430
|
for (const url of testUrls) {
|
|
5291
5431
|
try {
|
|
5292
5432
|
const resp = await fetch(url, { method: "GET", redirect: "follow" });
|
|
5293
|
-
if (resp.status === 404)
|
|
5294
|
-
proper404++;
|
|
5433
|
+
if (resp.status === 404) proper404++;
|
|
5295
5434
|
} catch {
|
|
5296
5435
|
}
|
|
5297
5436
|
}
|
|
@@ -5386,12 +5525,11 @@ var cacheHeaderHygiene = {
|
|
|
5386
5525
|
}
|
|
5387
5526
|
};
|
|
5388
5527
|
var urlStabilityChecks = [
|
|
5528
|
+
httpsEnabled,
|
|
5389
5529
|
httpStatusCodes,
|
|
5390
5530
|
redirectBehavior,
|
|
5391
5531
|
cacheHeaderHygiene
|
|
5392
5532
|
];
|
|
5393
|
-
|
|
5394
|
-
// node_modules/@agentimization/core/dist/checks/authentication.js
|
|
5395
5533
|
var authGateDetection = {
|
|
5396
5534
|
id: "auth-gate-detection",
|
|
5397
5535
|
name: "Auth Gate Detection",
|
|
@@ -5439,7 +5577,9 @@ var authAlternativeAccess = {
|
|
|
5439
5577
|
requiresNetwork: true,
|
|
5440
5578
|
run: async (ctx) => {
|
|
5441
5579
|
const pages = ctx.sampledPages.slice(0, 10);
|
|
5442
|
-
const gated = pages.filter(
|
|
5580
|
+
const gated = pages.filter(
|
|
5581
|
+
(p) => p.statusCode === 401 || p.statusCode === 403
|
|
5582
|
+
);
|
|
5443
5583
|
if (gated.length === 0) {
|
|
5444
5584
|
return {
|
|
5445
5585
|
id: "auth-alternative-access",
|
|
@@ -5474,28 +5614,18 @@ var authenticationChecks = [
|
|
|
5474
5614
|
authGateDetection,
|
|
5475
5615
|
authAlternativeAccess
|
|
5476
5616
|
];
|
|
5477
|
-
|
|
5478
|
-
// node_modules/@agentimization/core/dist/checks/geo-signals.js
|
|
5479
5617
|
var detectFramework = (pages) => {
|
|
5480
5618
|
for (const page of pages) {
|
|
5481
5619
|
const xpb = (page.headers["x-powered-by"] ?? "").toLowerCase();
|
|
5482
|
-
if (xpb.includes("next.js"))
|
|
5483
|
-
|
|
5484
|
-
if (xpb.includes("nuxt"))
|
|
5485
|
-
return "nuxt";
|
|
5620
|
+
if (xpb.includes("next.js")) return "next";
|
|
5621
|
+
if (xpb.includes("nuxt")) return "nuxt";
|
|
5486
5622
|
const html = page.html;
|
|
5487
|
-
if (/\/_next\/static\//.test(html) || /<script[^>]+id="__NEXT_DATA__"/.test(html))
|
|
5488
|
-
|
|
5489
|
-
if (/
|
|
5490
|
-
|
|
5491
|
-
if (/
|
|
5492
|
-
|
|
5493
|
-
if (/<meta\s+name="generator"\s+content="Astro/i.test(html))
|
|
5494
|
-
return "astro";
|
|
5495
|
-
if (/<meta\s+name="generator"\s+content="WordPress/i.test(html))
|
|
5496
|
-
return "wordpress";
|
|
5497
|
-
if (/\/build\/_assets\/.*\.js/.test(html) && /window\.__remixContext/.test(html))
|
|
5498
|
-
return "remix";
|
|
5623
|
+
if (/\/_next\/static\//.test(html) || /<script[^>]+id="__NEXT_DATA__"/.test(html)) return "next";
|
|
5624
|
+
if (/__NUXT__\s*=/.test(html) || /\/_nuxt\//.test(html)) return "nuxt";
|
|
5625
|
+
if (/data-sveltekit-/.test(html)) return "sveltekit";
|
|
5626
|
+
if (/<meta\s+name="generator"\s+content="Astro/i.test(html)) return "astro";
|
|
5627
|
+
if (/<meta\s+name="generator"\s+content="WordPress/i.test(html)) return "wordpress";
|
|
5628
|
+
if (/\/build\/_assets\/.*\.js/.test(html) && /window\.__remixContext/.test(html)) return "remix";
|
|
5499
5629
|
}
|
|
5500
5630
|
return null;
|
|
5501
5631
|
};
|
|
@@ -5508,8 +5638,7 @@ var FRAMEWORK_DOCS = {
|
|
|
5508
5638
|
wordpress: "https://yoast.com/structured-data-with-schema-org-the-ultimate-guide/"
|
|
5509
5639
|
};
|
|
5510
5640
|
var frameworkHint = (fw) => {
|
|
5511
|
-
if (fw && FRAMEWORK_DOCS[fw])
|
|
5512
|
-
return ` See: ${FRAMEWORK_DOCS[fw]}`;
|
|
5641
|
+
if (fw && FRAMEWORK_DOCS[fw]) return ` See: ${FRAMEWORK_DOCS[fw]}`;
|
|
5513
5642
|
return " See: https://schema.org/docs/gs.html";
|
|
5514
5643
|
};
|
|
5515
5644
|
var structuredDataCoverage = {
|
|
@@ -5528,8 +5657,7 @@ var structuredDataCoverage = {
|
|
|
5528
5657
|
withStructuredData++;
|
|
5529
5658
|
for (const item of jsonLd) {
|
|
5530
5659
|
const type = item?.["@type"];
|
|
5531
|
-
if (typeof type === "string")
|
|
5532
|
-
types.push(type);
|
|
5660
|
+
if (typeof type === "string") types.push(type);
|
|
5533
5661
|
}
|
|
5534
5662
|
}
|
|
5535
5663
|
}
|
|
@@ -5606,8 +5734,7 @@ var citationWorthiness = {
|
|
|
5606
5734
|
signals.withLists++;
|
|
5607
5735
|
pageCitable = true;
|
|
5608
5736
|
}
|
|
5609
|
-
if (pageCitable)
|
|
5610
|
-
citablePages++;
|
|
5737
|
+
if (pageCitable) citablePages++;
|
|
5611
5738
|
}
|
|
5612
5739
|
if (citablePages >= pages.length * 0.7) {
|
|
5613
5740
|
return {
|
|
@@ -5640,9 +5767,10 @@ var topicalAuthoritySignals = {
|
|
|
5640
5767
|
const pages = ctx.sampledPages.slice(0, 10);
|
|
5641
5768
|
let totalInternalLinks = 0;
|
|
5642
5769
|
let pagesWithGoodLinking = 0;
|
|
5770
|
+
const resolveBase = ctx.mode === "local" ? ctx.baseUrl.href : ctx.baseUrl.origin;
|
|
5643
5771
|
for (const page of pages) {
|
|
5644
|
-
const links = extractLinks(page.html,
|
|
5645
|
-
const internalLinks = ctx.mode === "local" ? links.filter((l) =>
|
|
5772
|
+
const links = extractLinks(page.html, resolveBase);
|
|
5773
|
+
const internalLinks = ctx.mode === "local" ? links.filter((l) => l.startsWith("file:")) : links.filter((l) => {
|
|
5646
5774
|
try {
|
|
5647
5775
|
return new URL(l).origin === ctx.baseUrl.origin;
|
|
5648
5776
|
} catch {
|
|
@@ -5650,8 +5778,7 @@ var topicalAuthoritySignals = {
|
|
|
5650
5778
|
}
|
|
5651
5779
|
});
|
|
5652
5780
|
totalInternalLinks += internalLinks.length;
|
|
5653
|
-
if (internalLinks.length >= 3)
|
|
5654
|
-
pagesWithGoodLinking++;
|
|
5781
|
+
if (internalLinks.length >= 3) pagesWithGoodLinking++;
|
|
5655
5782
|
}
|
|
5656
5783
|
const avgLinks = pages.length > 0 ? Math.round(totalInternalLinks / pages.length) : 0;
|
|
5657
5784
|
if (avgLinks >= 5 && pagesWithGoodLinking >= pages.length * 0.7) {
|
|
@@ -5737,12 +5864,11 @@ var eeatSignals = {
|
|
|
5737
5864
|
return !!obj?.author;
|
|
5738
5865
|
});
|
|
5739
5866
|
const hasAuthorHtml = /class=["'][^"']*author[^"']*["']|rel=["']author["']/i.test(page.html);
|
|
5740
|
-
if (hasAuthorMeta || hasAuthorJsonLd || hasAuthorHtml)
|
|
5741
|
-
withAuthor++;
|
|
5867
|
+
if (hasAuthorMeta || hasAuthorJsonLd || hasAuthorHtml) withAuthor++;
|
|
5742
5868
|
const hasCredentials = /Ph\.?D|M\.?D|CPA|certified|licensed|expert|specialist/i.test(page.html);
|
|
5743
|
-
const
|
|
5744
|
-
|
|
5745
|
-
|
|
5869
|
+
const linkBase = ctx.mode === "local" ? ctx.baseUrl.href : ctx.baseUrl.origin;
|
|
5870
|
+
const hasAboutPage = extractLinks(page.html, linkBase).some((l) => /about|team|author/i.test(l));
|
|
5871
|
+
if (hasCredentials || hasAboutPage) withExpertise++;
|
|
5746
5872
|
}
|
|
5747
5873
|
const score = (withAuthor + withExpertise) / (pages.length * 2);
|
|
5748
5874
|
if (score >= 0.6) {
|
|
@@ -5782,8 +5908,7 @@ var faqSchema = {
|
|
|
5782
5908
|
return type === "FAQPage" || type === "QAPage";
|
|
5783
5909
|
});
|
|
5784
5910
|
const hasFaqHtml = /<details|<summary|class=["'][^"']*faq[^"']*["']|id=["'][^"']*faq[^"']*["']/i.test(page.html);
|
|
5785
|
-
if (hasFaqSchema || hasFaqHtml)
|
|
5786
|
-
withFaq++;
|
|
5911
|
+
if (hasFaqSchema || hasFaqHtml) withFaq++;
|
|
5787
5912
|
}
|
|
5788
5913
|
if (withFaq > 0) {
|
|
5789
5914
|
return {
|
|
@@ -5806,6 +5931,185 @@ var faqSchema = {
|
|
|
5806
5931
|
};
|
|
5807
5932
|
}
|
|
5808
5933
|
};
|
|
5934
|
+
var metaDescription = {
|
|
5935
|
+
id: "meta-description",
|
|
5936
|
+
name: "Meta Description",
|
|
5937
|
+
category: "geo-signals",
|
|
5938
|
+
description: "Checks for a meta description between 50 and 160 characters",
|
|
5939
|
+
weight: 0.5,
|
|
5940
|
+
run: async (ctx) => {
|
|
5941
|
+
const pages = ctx.sampledPages.slice(0, 10);
|
|
5942
|
+
if (pages.length === 0) {
|
|
5943
|
+
return {
|
|
5944
|
+
id: "meta-description",
|
|
5945
|
+
name: "Meta Description",
|
|
5946
|
+
category: "geo-signals",
|
|
5947
|
+
status: "skip",
|
|
5948
|
+
message: "No pages sampled"
|
|
5949
|
+
};
|
|
5950
|
+
}
|
|
5951
|
+
let withGoodDescription = 0;
|
|
5952
|
+
let missing = 0;
|
|
5953
|
+
let tooShort = 0;
|
|
5954
|
+
let tooLong = 0;
|
|
5955
|
+
for (const page of pages) {
|
|
5956
|
+
const meta = extractMetaTags(page.html);
|
|
5957
|
+
const description = meta["description"]?.trim();
|
|
5958
|
+
if (!description) {
|
|
5959
|
+
missing++;
|
|
5960
|
+
continue;
|
|
5961
|
+
}
|
|
5962
|
+
const len = description.length;
|
|
5963
|
+
if (len >= 50 && len <= 160) withGoodDescription++;
|
|
5964
|
+
else if (len < 50) tooShort++;
|
|
5965
|
+
else tooLong++;
|
|
5966
|
+
}
|
|
5967
|
+
if (withGoodDescription === pages.length) {
|
|
5968
|
+
return {
|
|
5969
|
+
id: "meta-description",
|
|
5970
|
+
name: "Meta Description",
|
|
5971
|
+
category: "geo-signals",
|
|
5972
|
+
status: "pass",
|
|
5973
|
+
message: `All ${pages.length} pages have a meta description between 50\u2013160 characters`,
|
|
5974
|
+
metadata: { withGoodDescription }
|
|
5975
|
+
};
|
|
5976
|
+
}
|
|
5977
|
+
if (missing === pages.length) {
|
|
5978
|
+
return {
|
|
5979
|
+
id: "meta-description",
|
|
5980
|
+
name: "Meta Description",
|
|
5981
|
+
category: "geo-signals",
|
|
5982
|
+
status: "fail",
|
|
5983
|
+
message: "No meta description found on any sampled page",
|
|
5984
|
+
suggestion: 'Add a <meta name="description"> between 50 and 160 characters to every page. Generative engines quote meta descriptions when summarizing your content.'
|
|
5985
|
+
};
|
|
5986
|
+
}
|
|
5987
|
+
const detail = [
|
|
5988
|
+
missing > 0 ? `${missing} missing` : null,
|
|
5989
|
+
tooShort > 0 ? `${tooShort} too short` : null,
|
|
5990
|
+
tooLong > 0 ? `${tooLong} too long` : null
|
|
5991
|
+
].filter(Boolean).join(" \xB7 ");
|
|
5992
|
+
return {
|
|
5993
|
+
id: "meta-description",
|
|
5994
|
+
name: "Meta Description",
|
|
5995
|
+
category: "geo-signals",
|
|
5996
|
+
status: missing >= pages.length / 2 ? "fail" : "warn",
|
|
5997
|
+
message: `${withGoodDescription}/${pages.length} pages have meta descriptions in the 50\u2013160 char range${detail ? ` \xB7 ${detail}` : ""}`,
|
|
5998
|
+
suggestion: missing > 0 ? 'Add a <meta name="description"> between 50 and 160 characters to every page. Some pages are missing it entirely.' : "Aim for 50\u2013160 characters. Shorter descriptions lack context for AI; longer ones get truncated.",
|
|
5999
|
+
metadata: { withGoodDescription, missing, tooShort, tooLong }
|
|
6000
|
+
};
|
|
6001
|
+
}
|
|
6002
|
+
};
|
|
6003
|
+
var openGraphTags = {
|
|
6004
|
+
id: "open-graph-tags",
|
|
6005
|
+
name: "Open Graph Tags",
|
|
6006
|
+
category: "geo-signals",
|
|
6007
|
+
description: "Checks for og:title, og:description, og:image, and og:url",
|
|
6008
|
+
weight: 0.5,
|
|
6009
|
+
run: async (ctx) => {
|
|
6010
|
+
const pages = ctx.sampledPages.slice(0, 10);
|
|
6011
|
+
if (pages.length === 0) {
|
|
6012
|
+
return {
|
|
6013
|
+
id: "open-graph-tags",
|
|
6014
|
+
name: "Open Graph Tags",
|
|
6015
|
+
category: "geo-signals",
|
|
6016
|
+
status: "skip",
|
|
6017
|
+
message: "No pages sampled"
|
|
6018
|
+
};
|
|
6019
|
+
}
|
|
6020
|
+
const required = ["og:title", "og:description", "og:image", "og:url"];
|
|
6021
|
+
let fullCoverage = 0;
|
|
6022
|
+
let partialCoverage = 0;
|
|
6023
|
+
const missingCounts = { "og:title": 0, "og:description": 0, "og:image": 0, "og:url": 0 };
|
|
6024
|
+
for (const page of pages) {
|
|
6025
|
+
const meta = extractMetaTags(page.html);
|
|
6026
|
+
const missing = required.filter((tag) => !meta[tag]);
|
|
6027
|
+
for (const tag of missing) missingCounts[tag] = (missingCounts[tag] ?? 0) + 1;
|
|
6028
|
+
if (missing.length === 0) fullCoverage++;
|
|
6029
|
+
else if (missing.length < required.length) partialCoverage++;
|
|
6030
|
+
}
|
|
6031
|
+
if (fullCoverage === pages.length) {
|
|
6032
|
+
return {
|
|
6033
|
+
id: "open-graph-tags",
|
|
6034
|
+
name: "Open Graph Tags",
|
|
6035
|
+
category: "geo-signals",
|
|
6036
|
+
status: "pass",
|
|
6037
|
+
message: `All ${pages.length} pages have complete Open Graph tags`
|
|
6038
|
+
};
|
|
6039
|
+
}
|
|
6040
|
+
const mostMissing = Object.entries(missingCounts).filter(([, n]) => n > 0).sort(([, a], [, b]) => b - a).map(([tag]) => tag);
|
|
6041
|
+
const noneCovered = pages.length - fullCoverage - partialCoverage;
|
|
6042
|
+
return {
|
|
6043
|
+
id: "open-graph-tags",
|
|
6044
|
+
name: "Open Graph Tags",
|
|
6045
|
+
category: "geo-signals",
|
|
6046
|
+
status: fullCoverage + partialCoverage === 0 ? "fail" : "warn",
|
|
6047
|
+
message: `${fullCoverage}/${pages.length} pages have complete Open Graph tags${partialCoverage > 0 ? ` \xB7 ${partialCoverage} partial` : ""}${noneCovered > 0 ? ` \xB7 ${noneCovered} with none` : ""}${mostMissing.length > 0 ? ` \xB7 most often missing: ${mostMissing.slice(0, 2).join(", ")}` : ""}`,
|
|
6048
|
+
suggestion: "Add og:title, og:description, og:image, and og:url to every page. AI engines and link previews use these to render rich citations of your content.",
|
|
6049
|
+
metadata: { fullCoverage, partialCoverage, noneCovered, missingCounts }
|
|
6050
|
+
};
|
|
6051
|
+
}
|
|
6052
|
+
};
|
|
6053
|
+
var externalCitations = {
|
|
6054
|
+
id: "external-citations",
|
|
6055
|
+
name: "External Citations",
|
|
6056
|
+
category: "geo-signals",
|
|
6057
|
+
description: "Checks for at least 2 outbound links to external sources per page",
|
|
6058
|
+
weight: 0.5,
|
|
6059
|
+
run: async (ctx) => {
|
|
6060
|
+
if (ctx.mode === "local") {
|
|
6061
|
+
return {
|
|
6062
|
+
id: "external-citations",
|
|
6063
|
+
name: "External Citations",
|
|
6064
|
+
category: "geo-signals",
|
|
6065
|
+
status: "info",
|
|
6066
|
+
message: "External link detection requires a live origin to compare against"
|
|
6067
|
+
};
|
|
6068
|
+
}
|
|
6069
|
+
const pages = ctx.sampledPages.slice(0, 10);
|
|
6070
|
+
if (pages.length === 0) {
|
|
6071
|
+
return {
|
|
6072
|
+
id: "external-citations",
|
|
6073
|
+
name: "External Citations",
|
|
6074
|
+
category: "geo-signals",
|
|
6075
|
+
status: "skip",
|
|
6076
|
+
message: "No pages sampled"
|
|
6077
|
+
};
|
|
6078
|
+
}
|
|
6079
|
+
const origin = ctx.baseUrl.origin;
|
|
6080
|
+
let pagesWithCitations = 0;
|
|
6081
|
+
let totalExternal = 0;
|
|
6082
|
+
for (const page of pages) {
|
|
6083
|
+
const links = extractLinks(page.html, origin);
|
|
6084
|
+
const external = links.filter((l) => {
|
|
6085
|
+
const u = new URL(l);
|
|
6086
|
+
return u.protocol.startsWith("http") && u.origin !== origin;
|
|
6087
|
+
});
|
|
6088
|
+
totalExternal += external.length;
|
|
6089
|
+
if (external.length >= 2) pagesWithCitations++;
|
|
6090
|
+
}
|
|
6091
|
+
const avgExternal = Math.round(totalExternal / pages.length);
|
|
6092
|
+
if (pagesWithCitations >= pages.length * 0.7) {
|
|
6093
|
+
return {
|
|
6094
|
+
id: "external-citations",
|
|
6095
|
+
name: "External Citations",
|
|
6096
|
+
category: "geo-signals",
|
|
6097
|
+
status: "pass",
|
|
6098
|
+
message: `${pagesWithCitations}/${pages.length} pages have \u22652 outbound links (avg ${avgExternal}/page)`,
|
|
6099
|
+
metadata: { pagesWithCitations, avgExternal }
|
|
6100
|
+
};
|
|
6101
|
+
}
|
|
6102
|
+
return {
|
|
6103
|
+
id: "external-citations",
|
|
6104
|
+
name: "External Citations",
|
|
6105
|
+
category: "geo-signals",
|
|
6106
|
+
status: pagesWithCitations > 0 ? "warn" : "fail",
|
|
6107
|
+
message: `Only ${pagesWithCitations}/${pages.length} pages have \u22652 outbound links (avg ${avgExternal}/page)`,
|
|
6108
|
+
suggestion: "Add at least 2 outbound links to authoritative external sources per page. Citing sources signals credibility to generative engines, which weigh outbound links when deciding what to cite.",
|
|
6109
|
+
metadata: { pagesWithCitations, avgExternal }
|
|
6110
|
+
};
|
|
6111
|
+
}
|
|
6112
|
+
};
|
|
5809
6113
|
var canonicalUrlConsistency = {
|
|
5810
6114
|
id: "canonical-url-consistency",
|
|
5811
6115
|
name: "Canonical URL Consistency",
|
|
@@ -5813,6 +6117,15 @@ var canonicalUrlConsistency = {
|
|
|
5813
6117
|
description: "Checks if pages have consistent canonical URLs",
|
|
5814
6118
|
weight: 0.5,
|
|
5815
6119
|
run: async (ctx) => {
|
|
6120
|
+
if (ctx.mode === "local") {
|
|
6121
|
+
return {
|
|
6122
|
+
id: "canonical-url-consistency",
|
|
6123
|
+
name: "Canonical URL Consistency",
|
|
6124
|
+
category: "geo-signals",
|
|
6125
|
+
status: "info",
|
|
6126
|
+
message: "only meaningful for live urls. re-run against a deployed site to verify"
|
|
6127
|
+
};
|
|
6128
|
+
}
|
|
5816
6129
|
const pages = ctx.sampledPages.slice(0, 10);
|
|
5817
6130
|
let withCanonical = 0;
|
|
5818
6131
|
let selfReferencing = 0;
|
|
@@ -5867,10 +6180,11 @@ var geoSignalChecks = [
|
|
|
5867
6180
|
contentFreshness,
|
|
5868
6181
|
eeatSignals,
|
|
5869
6182
|
faqSchema,
|
|
6183
|
+
metaDescription,
|
|
6184
|
+
openGraphTags,
|
|
6185
|
+
externalCitations,
|
|
5870
6186
|
canonicalUrlConsistency
|
|
5871
6187
|
];
|
|
5872
|
-
|
|
5873
|
-
// node_modules/@agentimization/core/dist/checks/agent-protocols.js
|
|
5874
6188
|
var mcpServerCard = {
|
|
5875
6189
|
id: "mcp-server-card",
|
|
5876
6190
|
name: "MCP Server Card",
|
|
@@ -6004,12 +6318,9 @@ var contentSignals = {
|
|
|
6004
6318
|
}
|
|
6005
6319
|
const text = ctx.robotsTxt.toLowerCase();
|
|
6006
6320
|
const signals = [];
|
|
6007
|
-
if (/ai[-_]?train/i.test(ctx.robotsTxt))
|
|
6008
|
-
|
|
6009
|
-
if (/
|
|
6010
|
-
signals.push("ai-input");
|
|
6011
|
-
if (/content[-_]?signals?/i.test(ctx.robotsTxt))
|
|
6012
|
-
signals.push("content-signals");
|
|
6321
|
+
if (/ai[-_]?train/i.test(ctx.robotsTxt)) signals.push("ai-train");
|
|
6322
|
+
if (/ai[-_]?input/i.test(ctx.robotsTxt)) signals.push("ai-input");
|
|
6323
|
+
if (/content[-_]?signals?/i.test(ctx.robotsTxt)) signals.push("content-signals");
|
|
6013
6324
|
const hasGPTBot = text.includes("gptbot");
|
|
6014
6325
|
const hasClaudeBot = text.includes("claudebot") || text.includes("claude-web");
|
|
6015
6326
|
const hasGoogleExtended = text.includes("google-extended");
|
|
@@ -6244,8 +6555,6 @@ var agentProtocolChecks = [
|
|
|
6244
6555
|
agentSkillsIndex,
|
|
6245
6556
|
agentsMd
|
|
6246
6557
|
];
|
|
6247
|
-
|
|
6248
|
-
// node_modules/@agentimization/core/dist/checks/index.js
|
|
6249
6558
|
var ALL_CHECKS = [
|
|
6250
6559
|
...contentDiscoverabilityChecks,
|
|
6251
6560
|
...markdownAvailabilityChecks,
|
|
@@ -6256,13 +6565,31 @@ var ALL_CHECKS = [
|
|
|
6256
6565
|
...geoSignalChecks,
|
|
6257
6566
|
...agentProtocolChecks
|
|
6258
6567
|
];
|
|
6259
|
-
|
|
6260
|
-
|
|
6261
|
-
|
|
6262
|
-
|
|
6568
|
+
var readIfExists = (path) => {
|
|
6569
|
+
try {
|
|
6570
|
+
if (existsSync(path)) {
|
|
6571
|
+
return readFileSync(path, "utf-8");
|
|
6572
|
+
}
|
|
6573
|
+
} catch {
|
|
6574
|
+
}
|
|
6575
|
+
return void 0;
|
|
6576
|
+
};
|
|
6577
|
+
var findUpward = (start, names, maxDepth = 6) => {
|
|
6578
|
+
let current = start;
|
|
6579
|
+
for (let i = 0; i < maxDepth; i++) {
|
|
6580
|
+
for (const name of names) {
|
|
6581
|
+
const candidate = join(current, name);
|
|
6582
|
+
const value = readIfExists(candidate);
|
|
6583
|
+
if (value !== void 0) return value;
|
|
6584
|
+
}
|
|
6585
|
+
const parent = dirname(current);
|
|
6586
|
+
if (parent === current) break;
|
|
6587
|
+
current = parent;
|
|
6588
|
+
}
|
|
6589
|
+
return void 0;
|
|
6590
|
+
};
|
|
6263
6591
|
var walkDir = (dir, extensions, maxDepth = 10) => {
|
|
6264
|
-
if (maxDepth <= 0)
|
|
6265
|
-
return [];
|
|
6592
|
+
if (maxDepth <= 0) return [];
|
|
6266
6593
|
const results = [];
|
|
6267
6594
|
try {
|
|
6268
6595
|
const entries = readdirSync(dir, { withFileTypes: true });
|
|
@@ -6281,15 +6608,6 @@ var walkDir = (dir, extensions, maxDepth = 10) => {
|
|
|
6281
6608
|
}
|
|
6282
6609
|
return results;
|
|
6283
6610
|
};
|
|
6284
|
-
var readIfExists = (path) => {
|
|
6285
|
-
try {
|
|
6286
|
-
if (existsSync(path)) {
|
|
6287
|
-
return readFileSync(path, "utf-8");
|
|
6288
|
-
}
|
|
6289
|
-
} catch {
|
|
6290
|
-
}
|
|
6291
|
-
return void 0;
|
|
6292
|
-
};
|
|
6293
6611
|
var buildLocalContext = (dirPath, config) => {
|
|
6294
6612
|
const baseUrl = new URL(`file://${dirPath}`);
|
|
6295
6613
|
const robotsTxt = readIfExists(join(dirPath, "robots.txt"));
|
|
@@ -6299,7 +6617,7 @@ var buildLocalContext = (dirPath, config) => {
|
|
|
6299
6617
|
const mcpServerCard2 = readIfExists(join(dirPath, ".well-known", "mcp", "server-card.json"));
|
|
6300
6618
|
const apiCatalog2 = readIfExists(join(dirPath, ".well-known", "api-catalog"));
|
|
6301
6619
|
const agentSkillsIndex2 = readIfExists(join(dirPath, ".well-known", "agent-skills", "index.json"));
|
|
6302
|
-
const agentsMd2 =
|
|
6620
|
+
const agentsMd2 = findUpward(dirPath, ["AGENTS.md", "AGENT.md"]);
|
|
6303
6621
|
const sitemapUrls = sitemapXml ? parseSitemapUrls(sitemapXml) : [];
|
|
6304
6622
|
if (!sitemapXml && robotsTxt) {
|
|
6305
6623
|
const sitemapMatch = robotsTxt.match(/Sitemap:\s*(.+)/i);
|
|
@@ -6316,8 +6634,8 @@ var buildLocalContext = (dirPath, config) => {
|
|
|
6316
6634
|
}
|
|
6317
6635
|
const htmlFiles = walkDir(dirPath, /* @__PURE__ */ new Set([".html", ".htm"]));
|
|
6318
6636
|
const mdFiles = walkDir(dirPath, /* @__PURE__ */ new Set([".md", ".mdx"]));
|
|
6319
|
-
const
|
|
6320
|
-
const sampled =
|
|
6637
|
+
const sampleSource = htmlFiles.length > 0 ? htmlFiles : mdFiles;
|
|
6638
|
+
const sampled = sampleSource.slice(0, config.sampleSize);
|
|
6321
6639
|
const sampledPages = sampled.map((filePath) => {
|
|
6322
6640
|
const content = readFileSync(filePath, "utf-8");
|
|
6323
6641
|
const relPath = relative(dirPath, filePath);
|
|
@@ -6368,19 +6686,12 @@ var wrapMarkdownAsHtml = (md, title) => {
|
|
|
6368
6686
|
html = html.replace(/^([^<\n].+)$/gm, "<p>$1</p>");
|
|
6369
6687
|
return `<!DOCTYPE html><html><head><title>${title}</title></head><body><main>${html}</main></body></html>`;
|
|
6370
6688
|
};
|
|
6371
|
-
|
|
6372
|
-
// node_modules/@agentimization/core/dist/index.js
|
|
6373
6689
|
var computeGrade = (score) => {
|
|
6374
|
-
if (score >= 95)
|
|
6375
|
-
|
|
6376
|
-
if (score >=
|
|
6377
|
-
|
|
6378
|
-
if (score >=
|
|
6379
|
-
return "B";
|
|
6380
|
-
if (score >= 55)
|
|
6381
|
-
return "C";
|
|
6382
|
-
if (score >= 40)
|
|
6383
|
-
return "D";
|
|
6690
|
+
if (score >= 95) return "A+";
|
|
6691
|
+
if (score >= 85) return "A";
|
|
6692
|
+
if (score >= 70) return "B";
|
|
6693
|
+
if (score >= 55) return "C";
|
|
6694
|
+
if (score >= 40) return "D";
|
|
6384
6695
|
return "F";
|
|
6385
6696
|
};
|
|
6386
6697
|
var buildRemoteContext = async (targetUrl, config) => {
|
|
@@ -6389,7 +6700,15 @@ var buildRemoteContext = async (targetUrl, config) => {
|
|
|
6389
6700
|
emit({ type: "phase", phase: "fetching" });
|
|
6390
6701
|
const baseUrl = new URL(targetUrl);
|
|
6391
6702
|
const origin = baseUrl.origin;
|
|
6392
|
-
const [
|
|
6703
|
+
const [
|
|
6704
|
+
robotsResult,
|
|
6705
|
+
llmsResult,
|
|
6706
|
+
llmsFullResult,
|
|
6707
|
+
sitemapResult,
|
|
6708
|
+
mcpCardResult,
|
|
6709
|
+
apiCatalogResult,
|
|
6710
|
+
agentSkillsResult
|
|
6711
|
+
] = await Promise.allSettled([
|
|
6393
6712
|
fetchText(`${origin}/robots.txt`, config),
|
|
6394
6713
|
fetchText(`${origin}/llms.txt`, config),
|
|
6395
6714
|
fetchText(`${origin}/llms-full.txt`, config),
|
|
@@ -6523,7 +6842,9 @@ var runAudit = async (ctx, config, start) => {
|
|
|
6523
6842
|
overallScore = 50;
|
|
6524
6843
|
}
|
|
6525
6844
|
const categories = {};
|
|
6526
|
-
const activeCategories = config.categories.filter(
|
|
6845
|
+
const activeCategories = config.categories.filter(
|
|
6846
|
+
(cat) => results.some((r) => r.category === cat)
|
|
6847
|
+
);
|
|
6527
6848
|
for (const cat of activeCategories) {
|
|
6528
6849
|
const catResults = results.filter((r) => r.category === cat);
|
|
6529
6850
|
const catScorable = catResults.filter((r) => r.status !== "skip" && r.status !== "info");
|
|
@@ -7230,11 +7551,11 @@ var copyToClipboard = async (text) => {
|
|
|
7230
7551
|
// src/ui/action-menu.tsx
|
|
7231
7552
|
import { writeFileSync } from "fs";
|
|
7232
7553
|
import { resolve } from "path";
|
|
7233
|
-
import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
7554
|
+
import { Fragment as Fragment2, jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
7234
7555
|
var FEEDBACK_OK = "oklch(0.858 0.109 142.7)";
|
|
7235
7556
|
var FEEDBACK_ERR = "oklch(0.718 0.181 10.0)";
|
|
7236
|
-
var
|
|
7237
|
-
{ label: "Copy fix prompt to clipboard", value: "copy", hint: "paste into Claude, ChatGPT, etc." },
|
|
7557
|
+
var buildMenuOptions = (hasIssues) => [
|
|
7558
|
+
...hasIssues ? [{ label: "Copy fix prompt to clipboard", value: "copy", hint: "paste into Claude, ChatGPT, etc." }] : [],
|
|
7238
7559
|
{ label: "Save JSON report", value: "json", hint: "agentimization-report.json" },
|
|
7239
7560
|
{ label: "Run another URL or path", value: "retry" },
|
|
7240
7561
|
{ label: "Exit", value: "exit" }
|
|
@@ -7251,6 +7572,8 @@ var ActionMenu = ({
|
|
|
7251
7572
|
const [feedback, setFeedback] = useState2(null);
|
|
7252
7573
|
const [input, setInput] = useState2("");
|
|
7253
7574
|
const [done, setDone] = useState2(false);
|
|
7575
|
+
const hasIssues = result.summary.failed > 0 || result.summary.warned > 0;
|
|
7576
|
+
const menuOptions = buildMenuOptions(hasIssues);
|
|
7254
7577
|
const handleAction = (action) => {
|
|
7255
7578
|
const promptOpts = { mode: isLocal ? "local" : "remote", target };
|
|
7256
7579
|
switch (action) {
|
|
@@ -7285,6 +7608,7 @@ var ActionMenu = ({
|
|
|
7285
7608
|
setDone(true);
|
|
7286
7609
|
setTimeout(() => {
|
|
7287
7610
|
exit();
|
|
7611
|
+
process.stdout.write("\n");
|
|
7288
7612
|
if (result.overall_score < 50) process.exit(1);
|
|
7289
7613
|
}, 50);
|
|
7290
7614
|
break;
|
|
@@ -7297,13 +7621,16 @@ var ActionMenu = ({
|
|
|
7297
7621
|
if (key.upArrow) {
|
|
7298
7622
|
setSelected((prev) => Math.max(0, prev - 1));
|
|
7299
7623
|
} else if (key.downArrow) {
|
|
7300
|
-
setSelected((prev) => Math.min(
|
|
7624
|
+
setSelected((prev) => Math.min(menuOptions.length - 1, prev + 1));
|
|
7301
7625
|
} else if (key.return) {
|
|
7302
|
-
const option =
|
|
7626
|
+
const option = menuOptions[selected];
|
|
7303
7627
|
handleAction(option.value);
|
|
7304
7628
|
} else if (char === "q" || key.escape) {
|
|
7305
7629
|
setDone(true);
|
|
7306
|
-
setTimeout(() =>
|
|
7630
|
+
setTimeout(() => {
|
|
7631
|
+
exit();
|
|
7632
|
+
process.stdout.write("\n");
|
|
7633
|
+
}, 50);
|
|
7307
7634
|
}
|
|
7308
7635
|
return;
|
|
7309
7636
|
}
|
|
@@ -7337,7 +7664,7 @@ var ActionMenu = ({
|
|
|
7337
7664
|
return /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", marginTop: 1, children: [
|
|
7338
7665
|
/* @__PURE__ */ jsx4(Text4, { color: toInkColor(TEXT_DIM), children: " \u2500\u2500\u2500 What next? \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500" }),
|
|
7339
7666
|
/* @__PURE__ */ jsx4(Text4, { color: toInkColor(TEXT_DIM), children: " Use \u2191\u2193 arrows, enter to select, q to quit" }),
|
|
7340
|
-
/* @__PURE__ */ jsx4(Box4, { flexDirection: "column", marginTop: 1, children:
|
|
7667
|
+
/* @__PURE__ */ jsx4(Box4, { flexDirection: "column", marginTop: 1, children: menuOptions.map((opt, i) => {
|
|
7341
7668
|
const isSelected = i === selected;
|
|
7342
7669
|
return /* @__PURE__ */ jsxs4(Box4, { paddingLeft: 2, children: [
|
|
7343
7670
|
/* @__PURE__ */ jsxs4(Text4, { color: isSelected ? toInkColor(ACCENT_BLUE) : void 0, children: [
|
|
@@ -7352,7 +7679,10 @@ var ActionMenu = ({
|
|
|
7352
7679
|
] }) : null
|
|
7353
7680
|
] }, opt.value);
|
|
7354
7681
|
}) }),
|
|
7355
|
-
feedback ? /* @__PURE__ */
|
|
7682
|
+
feedback ? /* @__PURE__ */ jsxs4(Fragment2, { children: [
|
|
7683
|
+
/* @__PURE__ */ jsx4(Box4, { marginTop: 1, paddingLeft: 2, children: /* @__PURE__ */ jsx4(Text4, { color: toInkColor(feedback.startsWith("\u2713") ? FEEDBACK_OK : FEEDBACK_ERR), children: feedback }) }),
|
|
7684
|
+
/* @__PURE__ */ jsx4(Box4, { height: 1, children: /* @__PURE__ */ jsx4(Text4, { children: " " }) })
|
|
7685
|
+
] }) : null
|
|
7356
7686
|
] });
|
|
7357
7687
|
};
|
|
7358
7688
|
|
|
@@ -7433,7 +7763,7 @@ var ErrorActions = ({ onRetry }) => {
|
|
|
7433
7763
|
|
|
7434
7764
|
// src/ui/target.ts
|
|
7435
7765
|
import { existsSync as existsSync2, statSync } from "fs";
|
|
7436
|
-
import { resolve as resolve2 } from "path";
|
|
7766
|
+
import { join as join2, resolve as resolve2 } from "path";
|
|
7437
7767
|
var isLocalPath = (arg) => {
|
|
7438
7768
|
if (arg === "." || arg === ".." || arg.startsWith("./") || arg.startsWith("../") || arg.startsWith("/")) {
|
|
7439
7769
|
return true;
|
|
@@ -7449,11 +7779,30 @@ var normalizeUrl = (arg) => {
|
|
|
7449
7779
|
const parsed = new URL(arg.startsWith("http") ? arg : `https://${arg}`);
|
|
7450
7780
|
return parsed.href;
|
|
7451
7781
|
};
|
|
7782
|
+
var BUILD_DIR_NAMES = ["dist", "build", "out", "_site", "public"];
|
|
7783
|
+
var hasIndex = (dir) => ["index.html", "index.htm", "index.md"].some((name) => existsSync2(join2(dir, name)));
|
|
7784
|
+
var findBuildDir = (root) => {
|
|
7785
|
+
for (const name of BUILD_DIR_NAMES) {
|
|
7786
|
+
const candidate = join2(root, name);
|
|
7787
|
+
if (existsSync2(candidate) && statSync(candidate).isDirectory() && hasIndex(candidate)) {
|
|
7788
|
+
return candidate;
|
|
7789
|
+
}
|
|
7790
|
+
}
|
|
7791
|
+
return void 0;
|
|
7792
|
+
};
|
|
7452
7793
|
var resolveTarget = (input) => {
|
|
7453
7794
|
const trimmed = input.trim();
|
|
7454
7795
|
if (!trimmed) return { error: "Please enter a URL or path." };
|
|
7455
7796
|
if (isLocalPath(trimmed)) {
|
|
7456
|
-
|
|
7797
|
+
const requested = resolve2(trimmed);
|
|
7798
|
+
if (hasIndex(requested)) {
|
|
7799
|
+
return { target: requested, isLocal: true };
|
|
7800
|
+
}
|
|
7801
|
+
const buildDir = findBuildDir(requested);
|
|
7802
|
+
if (buildDir) {
|
|
7803
|
+
return { target: buildDir, isLocal: true, autoDetectedFrom: requested };
|
|
7804
|
+
}
|
|
7805
|
+
return { target: requested, isLocal: true };
|
|
7457
7806
|
}
|
|
7458
7807
|
try {
|
|
7459
7808
|
return { target: normalizeUrl(trimmed), isLocal: false };
|
|
@@ -7464,7 +7813,13 @@ var resolveTarget = (input) => {
|
|
|
7464
7813
|
|
|
7465
7814
|
// src/ui/app.tsx
|
|
7466
7815
|
import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
7467
|
-
var App = ({
|
|
7816
|
+
var App = ({
|
|
7817
|
+
target: initialTarget,
|
|
7818
|
+
isLocal: initialIsLocal,
|
|
7819
|
+
categories,
|
|
7820
|
+
sampleSize,
|
|
7821
|
+
autoDetectedFrom
|
|
7822
|
+
}) => {
|
|
7468
7823
|
const [target, setTarget] = useState4(initialTarget);
|
|
7469
7824
|
const [isLocal, setIsLocal] = useState4(initialIsLocal);
|
|
7470
7825
|
const [phase, setPhase] = useState4("init");
|
|
@@ -7498,7 +7853,7 @@ var App = ({ target: initialTarget, isLocal: initialIsLocal, categories, sampleS
|
|
|
7498
7853
|
const config = { categories, sampleSize, onEvent };
|
|
7499
7854
|
const res = isLocal ? await auditLocal(target, config) : await audit(target, config);
|
|
7500
7855
|
if (isLocal) {
|
|
7501
|
-
setNetworkSkipped(
|
|
7856
|
+
setNetworkSkipped(ALL_CHECKS.length - res.summary.total);
|
|
7502
7857
|
}
|
|
7503
7858
|
setResult(res);
|
|
7504
7859
|
setPhase("done");
|
|
@@ -7573,6 +7928,11 @@ var App = ({ target: initialTarget, isLocal: initialIsLocal, categories, sampleS
|
|
|
7573
7928
|
gradeColor: result ? GRADE_COLORS[result.grade] : void 0
|
|
7574
7929
|
}
|
|
7575
7930
|
) }),
|
|
7931
|
+
autoDetectedFrom ? /* @__PURE__ */ jsxs6(Text6, { dimColor: true, children: [
|
|
7932
|
+
"auto-detected build output (was ",
|
|
7933
|
+
autoDetectedFrom,
|
|
7934
|
+
")"
|
|
7935
|
+
] }) : null,
|
|
7576
7936
|
isLocal && networkSkipped > 0 && phase === "done" ? /* @__PURE__ */ jsxs6(Text6, { dimColor: true, children: [
|
|
7577
7937
|
networkSkipped,
|
|
7578
7938
|
" network-only checks skipped"
|
|
@@ -7636,10 +7996,18 @@ Examples:
|
|
|
7636
7996
|
$ agentimization ./docs --category content-structure
|
|
7637
7997
|
$ agentimization ./public --md | pbcopy`).action(async (target, opts) => {
|
|
7638
7998
|
const categories = opts.category ? [opts.category] : void 0;
|
|
7639
|
-
const
|
|
7999
|
+
const resolved = resolveTarget(target);
|
|
8000
|
+
if ("error" in resolved) {
|
|
8001
|
+
console.error(`Error: ${resolved.error}`);
|
|
8002
|
+
return process.exit(1);
|
|
8003
|
+
}
|
|
8004
|
+
const { target: targetResolved, isLocal, autoDetectedFrom } = resolved;
|
|
8005
|
+
if (autoDetectedFrom && !opts.json) {
|
|
8006
|
+
console.error(`auditing built output at ${targetResolved}`);
|
|
8007
|
+
}
|
|
7640
8008
|
if (opts.json) {
|
|
7641
8009
|
try {
|
|
7642
|
-
const result = isLocal ? await auditLocal(
|
|
8010
|
+
const result = isLocal ? await auditLocal(targetResolved, { categories, sampleSize: opts.sampleSize }) : await audit(targetResolved, { categories, sampleSize: opts.sampleSize });
|
|
7643
8011
|
console.log(JSON.stringify(result, null, 2));
|
|
7644
8012
|
if (result.overall_score < 50) process.exit(1);
|
|
7645
8013
|
} catch (error) {
|
|
@@ -7651,8 +8019,8 @@ Examples:
|
|
|
7651
8019
|
}
|
|
7652
8020
|
if (opts.md) {
|
|
7653
8021
|
try {
|
|
7654
|
-
const result = isLocal ? await auditLocal(
|
|
7655
|
-
console.log(generateAgentPrompt(result, { mode: isLocal ? "local" : "remote", target }));
|
|
8022
|
+
const result = isLocal ? await auditLocal(targetResolved, { categories, sampleSize: opts.sampleSize }) : await audit(targetResolved, { categories, sampleSize: opts.sampleSize });
|
|
8023
|
+
console.log(generateAgentPrompt(result, { mode: isLocal ? "local" : "remote", target: targetResolved }));
|
|
7656
8024
|
if (result.overall_score < 50) process.exit(1);
|
|
7657
8025
|
} catch (error) {
|
|
7658
8026
|
const msg = error instanceof Error ? error.message : "Unknown error";
|
|
@@ -7661,24 +8029,16 @@ Examples:
|
|
|
7661
8029
|
}
|
|
7662
8030
|
return;
|
|
7663
8031
|
}
|
|
7664
|
-
const targetResolved = isLocal ? resolve3(target) : normalizeUrl2(target);
|
|
7665
8032
|
render(
|
|
7666
8033
|
React2.createElement(App, {
|
|
7667
8034
|
target: targetResolved,
|
|
7668
8035
|
isLocal,
|
|
7669
8036
|
categories,
|
|
7670
|
-
sampleSize: opts.sampleSize
|
|
8037
|
+
sampleSize: opts.sampleSize,
|
|
8038
|
+
autoDetectedFrom
|
|
7671
8039
|
})
|
|
7672
8040
|
);
|
|
7673
8041
|
});
|
|
7674
|
-
var normalizeUrl2 = (arg) => {
|
|
7675
|
-
try {
|
|
7676
|
-
return normalizeUrl(arg);
|
|
7677
|
-
} catch {
|
|
7678
|
-
console.error(`Error: Invalid URL "${arg}"`);
|
|
7679
|
-
process.exit(1);
|
|
7680
|
-
}
|
|
7681
|
-
};
|
|
7682
8042
|
try {
|
|
7683
8043
|
program.parse();
|
|
7684
8044
|
} catch (err) {
|