@forkpoint/agent-lighthouse-core 0.2.0 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,27 @@
1
+ # @forkpoint/agent-lighthouse-core
2
+
3
+ Core TypeScript scanner engine for Agent Lighthouse.
4
+
5
+ Use this package when you want to embed AI-agent readiness audits in your own service, build tooling, CI system, or reporting flow.
6
+
7
+ ## Usage
8
+
9
+ ```typescript
10
+ import { runScan } from "@forkpoint/agent-lighthouse-core";
11
+
12
+ const report = await runScan("https://example.com");
13
+
14
+ console.log(report.overallScore);
15
+ ```
16
+
17
+ The engine runs deterministic audits for `llms.txt`, AI crawler access, Schema.org, WebMCP, OpenAPI discovery, AEO/GEO content structure, accessibility, and technical readiness.
18
+
19
+ ## Links
20
+
21
+ - Documentation: https://forkpoint.github.io/agent-lighthouse/
22
+ - Repository: https://github.com/ForkPoint/agent-lighthouse
23
+ - CLI package: https://www.npmjs.com/package/@forkpoint/agent-lighthouse
24
+
25
+ ## License
26
+
27
+ GPL-3.0-only
package/dist/index.d.mts CHANGED
@@ -368,6 +368,7 @@ declare function extractImages($: CheerioAPI): Array<{
368
368
  height?: string;
369
369
  loading?: string;
370
370
  role?: string;
371
+ ariaHidden?: string;
371
372
  }>;
372
373
  declare function extractForms($: CheerioAPI): Array<{
373
374
  action: string;
package/dist/index.d.ts CHANGED
@@ -368,6 +368,7 @@ declare function extractImages($: CheerioAPI): Array<{
368
368
  height?: string;
369
369
  loading?: string;
370
370
  role?: string;
371
+ ariaHidden?: string;
371
372
  }>;
372
373
  declare function extractForms($: CheerioAPI): Array<{
373
374
  action: string;
package/dist/index.js CHANGED
@@ -603,7 +603,8 @@ function extractImages($) {
603
603
  width: $(el).attr("width") ?? void 0,
604
604
  height: $(el).attr("height") ?? void 0,
605
605
  loading: $(el).attr("loading") ?? void 0,
606
- role: $(el).attr("role") ?? void 0
606
+ role: $(el).attr("role") ?? void 0,
607
+ ariaHidden: $(el).attr("aria-hidden") ?? void 0
607
608
  });
608
609
  });
609
610
  return images;
@@ -4743,9 +4744,6 @@ function matchesAnyType2(schema, types) {
4743
4744
  function allSchemas3(ctx) {
4744
4745
  return ctx.pages.flatMap((p) => flattenJsonLd(p.structuredData ?? p.jsonLd));
4745
4746
  }
4746
- function hasProps3(obj, keys) {
4747
- return keys.filter((k) => !obj[k]);
4748
- }
4749
4747
  var ServiceProductSchemaAudit = class extends Audit {
4750
4748
  static meta = {
4751
4749
  id: "3.8",
@@ -4798,13 +4796,19 @@ var ServiceProductSchemaAudit = class extends Audit {
4798
4796
  }
4799
4797
  );
4800
4798
  }
4801
- const requiredProps = ["name", "description", "provider"];
4802
4799
  const first2 = serviceProducts[0];
4803
- const missing = hasProps3(first2, requiredProps);
4800
+ const isProduct = matchesAnyType2(first2, ["Product"]);
4801
+ const hasName = Boolean(first2["name"]);
4802
+ const hasDescription = Boolean(first2["description"]);
4803
+ const hasProviderOrBrand = isProduct ? Boolean(first2["brand"] || first2["manufacturer"] || first2["provider"] || first2["offers"]) : Boolean(first2["provider"]);
4804
+ const missing = [];
4805
+ if (!hasName) missing.push("name");
4806
+ if (!hasDescription) missing.push("description");
4807
+ if (!hasProviderOrBrand) missing.push(isProduct ? "brand/offers" : "provider");
4804
4808
  const valid = missing.length === 0;
4805
4809
  if (valid) {
4806
4810
  return this.pass(
4807
- `Service/Product schema found with name, description, and provider.`,
4811
+ `Service/Product schema found with name, description, and ${isProduct ? "brand/offers" : "provider"}.`,
4808
4812
  "Service or Product schema with name, description, and provider.",
4809
4813
  `Complete Service/Product schema (${serviceProducts.length} total)`
4810
4814
  );
@@ -5450,7 +5454,7 @@ function matchesAnyType7(schema, types) {
5450
5454
  function allSchemas8(ctx) {
5451
5455
  return ctx.pages.flatMap((p) => flattenJsonLd(p.structuredData ?? p.jsonLd));
5452
5456
  }
5453
- function hasProps4(obj, keys) {
5457
+ function hasProps3(obj, keys) {
5454
5458
  return keys.filter((k) => !obj[k]);
5455
5459
  }
5456
5460
  var AuthorSchemaAudit = class extends Audit {
@@ -5531,11 +5535,11 @@ var AuthorSchemaAudit = class extends Audit {
5531
5535
  }
5532
5536
  const requiredProps = ["name", "jobTitle", "sameAs", "affiliation"];
5533
5537
  const bestPerson = allPersons.reduce((best, p) => {
5534
- const bestMissing = hasProps4(best, requiredProps).length;
5535
- const currentMissing = hasProps4(p, requiredProps).length;
5538
+ const bestMissing = hasProps3(best, requiredProps).length;
5539
+ const currentMissing = hasProps3(p, requiredProps).length;
5536
5540
  return currentMissing < bestMissing ? p : best;
5537
5541
  }, allPersons[0]);
5538
- const missing = hasProps4(bestPerson, requiredProps);
5542
+ const missing = hasProps3(bestPerson, requiredProps);
5539
5543
  const complete = missing.length === 0;
5540
5544
  if (complete) {
5541
5545
  return this.pass(
@@ -11456,7 +11460,7 @@ var DecorativeImagesAudit = class extends Audit {
11456
11460
  for (const img of images) {
11457
11461
  if (img.alt === "") {
11458
11462
  decorativeCount++;
11459
- if (img.role === "presentation") {
11463
+ if (img.role === "presentation" || img.role === "none" || img.ariaHidden === "true") {
11460
11464
  correctlyMarked++;
11461
11465
  }
11462
11466
  }
@@ -12680,8 +12684,19 @@ var CorsAiFilesAudit = class extends Audit {
12680
12684
  async audit(ctx) {
12681
12685
  const page = ctx.pages?.[0];
12682
12686
  const aiPaths = ["/llms.txt", "/.well-known/ai-catalog.json"];
12687
+ const hasRootFiles = ctx.rootFiles && Object.keys(ctx.rootFiles).length > 0;
12688
+ const existingAiPaths = hasRootFiles ? aiPaths.filter((p) => ctx.rootFiles[p]?.status === 200) : aiPaths;
12689
+ if (hasRootFiles && existingAiPaths.length === 0) {
12690
+ return this.warn(
12691
+ "No AI files (/llms.txt, /.well-known/ai-catalog.json) found to check CORS headers.",
12692
+ "OPTIONS requests to /llms.txt and /.well-known/ai-catalog.json return Access-Control-Allow-Origin",
12693
+ "No applicable AI files found",
12694
+ void 0,
12695
+ page?.url
12696
+ );
12697
+ }
12683
12698
  const corsResults = [];
12684
- for (const path of aiPaths) {
12699
+ for (const path of existingAiPaths) {
12685
12700
  try {
12686
12701
  const result = await ctx.fetch({
12687
12702
  url: `${ctx.baseUrl}${path}`,
@@ -15171,9 +15186,33 @@ var AboutCredentialsAudit = class extends Audit {
15171
15186
  }
15172
15187
  };
15173
15188
  async audit(ctx) {
15174
- const aboutPaths = ["/about/", "/about-us/", "/about"];
15175
- let aboutResult = ctx.rootFiles["/about/"] ?? ctx.rootFiles["/about-us/"];
15176
- if (!aboutResult || aboutResult.status !== 200) {
15189
+ const aboutPaths = [
15190
+ "/about/",
15191
+ "/about-us/",
15192
+ "/about",
15193
+ "/pages/about",
15194
+ "/pages/about-us",
15195
+ "/pages/our-story",
15196
+ "/our-story"
15197
+ ];
15198
+ let aboutResult = void 0;
15199
+ for (const path of aboutPaths) {
15200
+ const file = ctx.rootFiles[path];
15201
+ if (file && file.status === 200 && file.body) {
15202
+ aboutResult = file;
15203
+ break;
15204
+ }
15205
+ }
15206
+ if (!aboutResult && ctx.pages) {
15207
+ const matchingPage = ctx.pages.find((p) => {
15208
+ const urlLower = p.url.toLowerCase();
15209
+ return urlLower.includes("/about") || urlLower.includes("/our-story") || urlLower.includes("/who-we-are");
15210
+ });
15211
+ if (matchingPage?.fetchResult?.status === 200 && matchingPage.fetchResult.body) {
15212
+ aboutResult = matchingPage.fetchResult;
15213
+ }
15214
+ }
15215
+ if (!aboutResult) {
15177
15216
  for (const path of aboutPaths) {
15178
15217
  try {
15179
15218
  const result = await ctx.fetch({
@@ -15442,7 +15481,11 @@ var TRUST_PATTERNS = [
15442
15481
  /\baward/i,
15443
15482
  /\bas\s+seen\s+(in|on)\b/i,
15444
15483
  /\bpartner(s|ed|ship)?\b/i,
15445
- /\bcertif(ied|icate|ication)\b/i
15484
+ /\bcertif(ied|icate|ication)\b/i,
15485
+ /\b(money[-\s]back\s+guarantee|satisfaction\s+guarantee|guaranteed)\b/i,
15486
+ /\b(reviews|customer\s+rating|verified\s+(buyer|customer|review)|happy\s+customers)\b/i,
15487
+ /\b(sustainable|organic|fair\s+trade|b\s+corp|handcrafted|handmade)\b/i,
15488
+ /\b(secure\s+checkout|free\s+returns|free\s+shipping)\b/i
15446
15489
  ];
15447
15490
  var TrustSignalsAudit = class extends Audit {
15448
15491
  static meta = {
@@ -22148,7 +22191,11 @@ async function runScan(url, onProgress, pageOverrides, signal) {
22148
22191
  "/policies/terms-of-service",
22149
22192
  "/about/",
22150
22193
  "/about-us/",
22151
- "/about"
22194
+ "/about",
22195
+ "/pages/about",
22196
+ "/pages/about-us",
22197
+ "/pages/our-story",
22198
+ "/our-story"
22152
22199
  ];
22153
22200
  logger.debug({ count: rootFilePaths.length }, "[orchestrator] Phase 1: Fetching root files");
22154
22201
  const rootResults = await Promise.all(