agentimization 0.1.0 → 0.1.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 +39 -155
- package/dist/index.js +186 -167
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,193 +1,77 @@
|
|
|
1
|
-
|
|
2
|
-
<img src="https://img.shields.io/npm/v/agentimization?style=flat-square&color=blue" alt="npm version" />
|
|
3
|
-
<img src="https://img.shields.io/badge/license-MIT-green?style=flat-square" alt="license" />
|
|
4
|
-
<img src="https://img.shields.io/badge/checks-35-purple?style=flat-square" alt="checks" />
|
|
5
|
-
</p>
|
|
1
|
+
# agentimization
|
|
6
2
|
|
|
7
|
-
|
|
3
|
+
[](https://www.npmjs.com/package/agentimization)
|
|
8
4
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
5
|
+
```text
|
|
6
|
+
╭───────────────────────────────────────────────╮
|
|
7
|
+
│ ▓░▒▓░░▒░▓▒░▓▓░▒░▓░░▒▓▒░▓░░▓▒░▓░▒░▓░░▒▓░░ │
|
|
8
|
+
│ ░▓▒░▓░░▒▓▒░▓░░▒▓▓░▒░▓▒░░▓▒░▓░▒░░▓▒░░▓░▒ │
|
|
9
|
+
│ ▓░▒▓░░▒▓▒░░▓░▒▓▒░░▓░░▓▒░▓░▒░░▓▒░▓░░▒▓░ │
|
|
10
|
+
│ ░▒▓░▒░▓▒░░▓░▒▓░░▒▓▒░░▓░▒▓░░▒▓░ agentimization │
|
|
11
|
+
╰───────────────────────────────────────────────╯
|
|
12
|
+
```
|
|
17
13
|
|
|
18
|
-
|
|
14
|
+
geo audit for agent-ready websites and projects.
|
|
19
15
|
|
|
20
|
-
|
|
16
|
+
geomaxx your site so ai agents can actually find, parse, and cite it.
|
|
21
17
|
|
|
22
|
-
##
|
|
18
|
+
## install
|
|
23
19
|
|
|
24
20
|
```bash
|
|
25
21
|
npx agentimization https://your-site.com
|
|
26
22
|
```
|
|
27
23
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
```bash
|
|
31
|
-
npm install -g agentimization
|
|
32
|
-
```
|
|
33
|
-
|
|
34
|
-
## Usage
|
|
24
|
+
## usage
|
|
35
25
|
|
|
36
|
-
|
|
26
|
+
audit a live site:
|
|
37
27
|
|
|
38
28
|
```bash
|
|
39
|
-
agentimization https://docs.
|
|
29
|
+
agentimization https://docs.your-site.com
|
|
40
30
|
```
|
|
41
31
|
|
|
42
|
-
|
|
43
|
-
╭───────────────────────────────────────────────╮
|
|
44
|
-
│ ▓░▒▓░░▒░▓▒░▓▓░▒░▓░░▒▓▒░▓░░▓▒░▓░▒░▓░░▒▓░░ │
|
|
45
|
-
│ ░▓▒░▓░░▒▓▒░▓░░▒▓▓░▒░▓▒░░▓▒░▓░▒░░▓▒░░▓░▒ │
|
|
46
|
-
│ ▓░▒▓░░▒▓▒░░▓░▒▓▒░░▓░░▓▒░▓░▒░░▓▒░▓░░▒▓░ │
|
|
47
|
-
│ ░▒▓░▒░▓▒░░▓░▒▓░░▒▓▒░░▓░▒▓░░▒▓░ agentimization │
|
|
48
|
-
│ │
|
|
49
|
-
│ https://docs.anthropic.com │
|
|
50
|
-
│ │
|
|
51
|
-
│ Crawling the site, one sec… │
|
|
52
|
-
╰───────────────────────────────────────────────╯
|
|
53
|
-
```
|
|
54
|
-
|
|
55
|
-
### Audit a local directory (great for CI)
|
|
32
|
+
audit a local directory:
|
|
56
33
|
|
|
57
34
|
```bash
|
|
58
35
|
agentimization .
|
|
59
|
-
agentimization ./docs
|
|
60
36
|
```
|
|
61
37
|
|
|
62
|
-
|
|
38
|
+
pipe results to a tool or file:
|
|
63
39
|
|
|
64
40
|
```bash
|
|
65
|
-
|
|
66
|
-
agentimization https://
|
|
67
|
-
|
|
68
|
-
# Markdown report — paste into Claude, ChatGPT, etc.
|
|
69
|
-
agentimization https://example.com --md
|
|
70
|
-
|
|
71
|
-
# Filter by category
|
|
72
|
-
agentimization https://example.com --category content-discoverability
|
|
41
|
+
agentimization https://your-site.com --json > report.json
|
|
42
|
+
agentimization https://your-site.com --md | pbcopy
|
|
73
43
|
```
|
|
74
44
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
Agentimization shows an interactive menu when the audit finishes:
|
|
78
|
-
|
|
79
|
-
- **Copy fix prompt to clipboard** — structured markdown an AI coding agent can use to fix your GEO issues
|
|
80
|
-
- **Save JSON report** — full audit data written to `agentimization-report.json`
|
|
81
|
-
- **Run another URL or path** — keep the session open and audit the next site
|
|
82
|
-
- **Exit**
|
|
83
|
-
|
|
84
|
-
## Checks
|
|
85
|
-
|
|
86
|
-
Agentimization runs **36 checks** across **8 categories**:
|
|
45
|
+
## what it checks
|
|
87
46
|
|
|
88
|
-
|
|
89
|
-
|---|---|
|
|
90
|
-
| **Content Discoverability** | `llms.txt` existence, structure, size, coverage, link resolution. Sitemap presence. `robots.txt` AI agent rules. |
|
|
91
|
-
| **Markdown Availability** | `.md` URL support, `Accept: text/markdown` content negotiation, HTML↔markdown parity. |
|
|
92
|
-
| **Content Structure** | Code fence validity, heading hierarchy, tabbed content serialization. |
|
|
93
|
-
| **Page Size & Rendering** | SSR vs CSR detection, HTML/markdown page size, content start position (boilerplate ratio). |
|
|
94
|
-
| **URL Stability** | HTTP status codes, redirect behavior, cache header hygiene. |
|
|
95
|
-
| **Authentication & Access** | Auth gate detection, alternative access paths for gated content. |
|
|
96
|
-
| **GEO Signals** | Structured data (JSON-LD), citation worthiness, topical authority, content freshness, E-E-A-T signals, FAQ schema, canonical URLs. |
|
|
97
|
-
| **Agent Protocols** | AGENTS.md, MCP server card, API catalog (RFC 9727), content signals (AI usage declarations), Link headers (RFC 8288), agent skills index. |
|
|
47
|
+
36 checks across 8 categories. each one is a thing ai agents need to discover, parse, or cite your content.
|
|
98
48
|
|
|
99
|
-
|
|
49
|
+
- content discoverability: `llms.txt`, sitemap, robots
|
|
50
|
+
- markdown availability: `.md` urls, content negotiation
|
|
51
|
+
- content structure: headings, code fences, hidden tabs
|
|
52
|
+
- page size and rendering: ssr vs csr, boilerplate ratio
|
|
53
|
+
- url stability: status codes, redirects, canonicals
|
|
54
|
+
- authentication and access: gates, alternative paths
|
|
55
|
+
- geo signals: json-ld, citations, freshness, e-e-a-t
|
|
56
|
+
- agent protocols: mcp card, api catalog, agents.md, link headers
|
|
100
57
|
|
|
101
|
-
|
|
58
|
+
## how it works
|
|
102
59
|
|
|
103
|
-
|
|
104
|
-
|---|---|
|
|
105
|
-
| A+ | 95–100 |
|
|
106
|
-
| A | 85–94 |
|
|
107
|
-
| B | 70–84 |
|
|
108
|
-
| C | 55–69 |
|
|
109
|
-
| D | 40–54 |
|
|
110
|
-
| F | 0–39 |
|
|
60
|
+
it samples up to 10 pages of your site, runs 36 checks against the html, headers, and well-known files, then weights them into a 0 to 100 score. failed checks come with a suggestion you can paste into your ai coding agent.
|
|
111
61
|
|
|
112
|
-
##
|
|
62
|
+
## requirements
|
|
113
63
|
|
|
114
|
-
|
|
64
|
+
node 18 or newer.
|
|
115
65
|
|
|
116
|
-
|
|
117
|
-
|---|---|---|---|
|
|
118
|
-
| `docs.anthropic.com` | **A** | 88 | Strong `llms.txt`, good markdown, structured data |
|
|
119
|
-
| `docs.stripe.com` | **A** | 91 | Excellent discoverability, markdown endpoints, great structure |
|
|
120
|
-
| `nextjs.org/docs` | **B** | 76 | Good SSR, missing `llms.txt`, decent GEO signals |
|
|
121
|
-
| `react.dev` | **B** | 72 | Good structure, no `llms.txt`, client-heavy rendering |
|
|
122
|
-
| `en.wikipedia.org` | **A** | 86 | Great content structure, strong citations, no `llms.txt` |
|
|
123
|
-
| `medium.com` | **D** | 45 | Auth gates, weak markdown, no `llms.txt` |
|
|
124
|
-
| `substack.com` | **C** | 58 | Mixed access, some content gated |
|
|
125
|
-
|
|
126
|
-
> These are illustrative examples. Run `agentimization <url>` to get real-time scores.
|
|
127
|
-
|
|
128
|
-
## Local mode
|
|
129
|
-
|
|
130
|
-
When you pass a directory path instead of a URL, Agentimization runs in **local mode**:
|
|
131
|
-
|
|
132
|
-
- Scans your files on disk (HTML, markdown, `llms.txt`, `robots.txt`, `sitemap.xml`)
|
|
133
|
-
- Skips network-only checks (content negotiation, auth detection, cache headers, etc.)
|
|
134
|
-
- Perfect as a **CI pre-deploy step** — catch GEO regressions before they ship
|
|
135
|
-
|
|
136
|
-
```bash
|
|
137
|
-
# In CI
|
|
138
|
-
agentimization . --json
|
|
139
|
-
# Exit code 1 if score < 50
|
|
140
|
-
```
|
|
141
|
-
|
|
142
|
-
## Programmatic API
|
|
66
|
+
## programmatic use
|
|
143
67
|
|
|
144
68
|
```typescript
|
|
145
|
-
import { audit
|
|
69
|
+
import { audit } from "@agentimization/core"
|
|
146
70
|
|
|
147
|
-
|
|
148
|
-
const result = await audit("https://docs.anthropic.com")
|
|
71
|
+
const result = await audit("https://your-site.com")
|
|
149
72
|
console.log(result.grade, result.overall_score)
|
|
150
|
-
|
|
151
|
-
// Local audit
|
|
152
|
-
const local = await auditLocal("./docs")
|
|
153
|
-
console.log(local.grade, local.overall_score)
|
|
154
|
-
|
|
155
|
-
// With options
|
|
156
|
-
const result = await audit("https://example.com", {
|
|
157
|
-
sampleSize: 20,
|
|
158
|
-
categories: ["content-discoverability", "geo-signals"],
|
|
159
|
-
onEvent: (event) => console.log(event),
|
|
160
|
-
})
|
|
161
|
-
```
|
|
162
|
-
|
|
163
|
-
## What is GEO?
|
|
164
|
-
|
|
165
|
-
**Generative Engine Optimization** is like SEO, but for AI. Instead of optimizing for Google's crawlers and ranking algorithm, GEO optimizes for AI agents that need to:
|
|
166
|
-
|
|
167
|
-
1. **Discover** your content (via `llms.txt`, sitemaps, `robots.txt`)
|
|
168
|
-
2. **Parse** it efficiently (markdown availability, clean HTML, SSR)
|
|
169
|
-
3. **Cite** it accurately (structured data, canonical URLs, E-E-A-T signals)
|
|
170
|
-
|
|
171
|
-
Sites that score well on Agentimization are more likely to be surfaced and cited by Claude, ChatGPT, Perplexity, and other generative engines.
|
|
172
|
-
|
|
173
|
-
## Contributing
|
|
174
|
-
|
|
175
|
-
```bash
|
|
176
|
-
git clone https://github.com/antlio/agentimization
|
|
177
|
-
cd agentimization
|
|
178
|
-
bun install
|
|
179
|
-
bun run build
|
|
180
|
-
bun run typecheck
|
|
181
|
-
```
|
|
182
|
-
|
|
183
|
-
The monorepo structure:
|
|
184
|
-
|
|
185
|
-
```
|
|
186
|
-
packages/shared — Types, schemas, constants
|
|
187
|
-
packages/core — Audit engine + all 36 checks
|
|
188
|
-
apps/cli — CLI (Commander.js + Ink)
|
|
189
73
|
```
|
|
190
74
|
|
|
191
|
-
##
|
|
75
|
+
## license
|
|
192
76
|
|
|
193
|
-
|
|
77
|
+
mit
|
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 = [];
|
|
@@ -4912,8 +4903,6 @@ var parseSitemapUrls = (xml) => {
|
|
|
4912
4903
|
}
|
|
4913
4904
|
return urls;
|
|
4914
4905
|
};
|
|
4915
|
-
|
|
4916
|
-
// node_modules/@agentimization/core/dist/checks/page-size.js
|
|
4917
4906
|
var MAX_HTML_CHARS = 5e4;
|
|
4918
4907
|
var MAX_MD_CHARS = 5e4;
|
|
4919
4908
|
var renderingStrategy = {
|
|
@@ -4935,8 +4924,7 @@ var renderingStrategy = {
|
|
|
4935
4924
|
}
|
|
4936
4925
|
let ssrCount = 0;
|
|
4937
4926
|
for (const page of pages) {
|
|
4938
|
-
if (hasServerRenderedContent(page.html))
|
|
4939
|
-
ssrCount++;
|
|
4927
|
+
if (hasServerRenderedContent(page.html)) ssrCount++;
|
|
4940
4928
|
}
|
|
4941
4929
|
if (ssrCount === pages.length) {
|
|
4942
4930
|
return {
|
|
@@ -5049,7 +5037,7 @@ var contentStartPosition = {
|
|
|
5049
5037
|
id: "content-start-position",
|
|
5050
5038
|
name: "Content Start Position",
|
|
5051
5039
|
category: "page-size",
|
|
5052
|
-
description: "Checks
|
|
5040
|
+
description: "Checks how soon main content starts relative to total HTML",
|
|
5053
5041
|
weight: 0.5,
|
|
5054
5042
|
run: async (ctx) => {
|
|
5055
5043
|
const pages = ctx.sampledPages.slice(0, 10);
|
|
@@ -5066,15 +5054,16 @@ var contentStartPosition = {
|
|
|
5066
5054
|
url: p.url,
|
|
5067
5055
|
position: findContentStartPosition(p.html)
|
|
5068
5056
|
}));
|
|
5069
|
-
const
|
|
5070
|
-
|
|
5071
|
-
|
|
5057
|
+
const medianPct = Math.round(
|
|
5058
|
+
positions.map((p) => p.position).sort((a, b) => a - b)[Math.floor(positions.length / 2)] * 100
|
|
5059
|
+
);
|
|
5060
|
+
if (medianPct <= 30) {
|
|
5072
5061
|
return {
|
|
5073
5062
|
id: "content-start-position",
|
|
5074
5063
|
name: "Content Start Position",
|
|
5075
5064
|
category: "page-size",
|
|
5076
5065
|
status: "pass",
|
|
5077
|
-
message: `
|
|
5066
|
+
message: `content starts at ${medianPct}% of html (median over ${pages.length} pages)`,
|
|
5078
5067
|
metadata: { medianPct }
|
|
5079
5068
|
};
|
|
5080
5069
|
}
|
|
@@ -5082,10 +5071,10 @@ var contentStartPosition = {
|
|
|
5082
5071
|
id: "content-start-position",
|
|
5083
5072
|
name: "Content Start Position",
|
|
5084
5073
|
category: "page-size",
|
|
5085
|
-
status: "warn",
|
|
5086
|
-
message: `
|
|
5087
|
-
suggestion: "
|
|
5088
|
-
metadata: { medianPct
|
|
5074
|
+
status: medianPct <= 50 ? "warn" : "fail",
|
|
5075
|
+
message: `content starts at ${medianPct}% of html (median over ${pages.length} pages)`,
|
|
5076
|
+
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.",
|
|
5077
|
+
metadata: { medianPct }
|
|
5089
5078
|
};
|
|
5090
5079
|
}
|
|
5091
5080
|
};
|
|
@@ -5095,8 +5084,6 @@ var pageSizeChecks = [
|
|
|
5095
5084
|
pageSizeMarkdown,
|
|
5096
5085
|
contentStartPosition
|
|
5097
5086
|
];
|
|
5098
|
-
|
|
5099
|
-
// node_modules/@agentimization/core/dist/checks/content-structure.js
|
|
5100
5087
|
var markdownCodeFenceValidity = {
|
|
5101
5088
|
id: "markdown-code-fence-validity",
|
|
5102
5089
|
name: "Markdown Code Fence Validity",
|
|
@@ -5111,8 +5098,7 @@ var markdownCodeFenceValidity = {
|
|
|
5111
5098
|
for (const page of ctx.sampledPages.slice(0, 10)) {
|
|
5112
5099
|
const codeBlockRegex = /<code[\s\S]*?<\/code>/gi;
|
|
5113
5100
|
const matches = page.html.match(codeBlockRegex);
|
|
5114
|
-
if (matches)
|
|
5115
|
-
totalFences2 += matches.length;
|
|
5101
|
+
if (matches) totalFences2 += matches.length;
|
|
5116
5102
|
}
|
|
5117
5103
|
if (totalFences2 === 0) {
|
|
5118
5104
|
return {
|
|
@@ -5178,8 +5164,7 @@ var sectionHeaderQuality = {
|
|
|
5178
5164
|
const issues = [];
|
|
5179
5165
|
for (const page of pages) {
|
|
5180
5166
|
const headings = extractHeadings(page.html);
|
|
5181
|
-
if (headings.length === 0)
|
|
5182
|
-
continue;
|
|
5167
|
+
if (headings.length === 0) continue;
|
|
5183
5168
|
let pageGood = true;
|
|
5184
5169
|
const hasH1 = headings.some((h) => h.level === 1);
|
|
5185
5170
|
if (!hasH1) {
|
|
@@ -5197,8 +5182,7 @@ var sectionHeaderQuality = {
|
|
|
5197
5182
|
break;
|
|
5198
5183
|
}
|
|
5199
5184
|
}
|
|
5200
|
-
if (pageGood)
|
|
5201
|
-
goodPages++;
|
|
5185
|
+
if (pageGood) goodPages++;
|
|
5202
5186
|
}
|
|
5203
5187
|
if (goodPages === pages.length) {
|
|
5204
5188
|
return {
|
|
@@ -5272,8 +5256,6 @@ var contentStructureChecks = [
|
|
|
5272
5256
|
sectionHeaderQuality,
|
|
5273
5257
|
tabbedContentSerialization
|
|
5274
5258
|
];
|
|
5275
|
-
|
|
5276
|
-
// node_modules/@agentimization/core/dist/checks/url-stability.js
|
|
5277
5259
|
var httpStatusCodes = {
|
|
5278
5260
|
id: "http-status-codes",
|
|
5279
5261
|
name: "HTTP Status Codes",
|
|
@@ -5290,8 +5272,7 @@ var httpStatusCodes = {
|
|
|
5290
5272
|
for (const url of testUrls) {
|
|
5291
5273
|
try {
|
|
5292
5274
|
const resp = await fetch(url, { method: "GET", redirect: "follow" });
|
|
5293
|
-
if (resp.status === 404)
|
|
5294
|
-
proper404++;
|
|
5275
|
+
if (resp.status === 404) proper404++;
|
|
5295
5276
|
} catch {
|
|
5296
5277
|
}
|
|
5297
5278
|
}
|
|
@@ -5390,8 +5371,6 @@ var urlStabilityChecks = [
|
|
|
5390
5371
|
redirectBehavior,
|
|
5391
5372
|
cacheHeaderHygiene
|
|
5392
5373
|
];
|
|
5393
|
-
|
|
5394
|
-
// node_modules/@agentimization/core/dist/checks/authentication.js
|
|
5395
5374
|
var authGateDetection = {
|
|
5396
5375
|
id: "auth-gate-detection",
|
|
5397
5376
|
name: "Auth Gate Detection",
|
|
@@ -5439,7 +5418,9 @@ var authAlternativeAccess = {
|
|
|
5439
5418
|
requiresNetwork: true,
|
|
5440
5419
|
run: async (ctx) => {
|
|
5441
5420
|
const pages = ctx.sampledPages.slice(0, 10);
|
|
5442
|
-
const gated = pages.filter(
|
|
5421
|
+
const gated = pages.filter(
|
|
5422
|
+
(p) => p.statusCode === 401 || p.statusCode === 403
|
|
5423
|
+
);
|
|
5443
5424
|
if (gated.length === 0) {
|
|
5444
5425
|
return {
|
|
5445
5426
|
id: "auth-alternative-access",
|
|
@@ -5474,28 +5455,18 @@ var authenticationChecks = [
|
|
|
5474
5455
|
authGateDetection,
|
|
5475
5456
|
authAlternativeAccess
|
|
5476
5457
|
];
|
|
5477
|
-
|
|
5478
|
-
// node_modules/@agentimization/core/dist/checks/geo-signals.js
|
|
5479
5458
|
var detectFramework = (pages) => {
|
|
5480
5459
|
for (const page of pages) {
|
|
5481
5460
|
const xpb = (page.headers["x-powered-by"] ?? "").toLowerCase();
|
|
5482
|
-
if (xpb.includes("next.js"))
|
|
5483
|
-
|
|
5484
|
-
if (xpb.includes("nuxt"))
|
|
5485
|
-
return "nuxt";
|
|
5461
|
+
if (xpb.includes("next.js")) return "next";
|
|
5462
|
+
if (xpb.includes("nuxt")) return "nuxt";
|
|
5486
5463
|
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";
|
|
5464
|
+
if (/\/_next\/static\//.test(html) || /<script[^>]+id="__NEXT_DATA__"/.test(html)) return "next";
|
|
5465
|
+
if (/__NUXT__\s*=/.test(html) || /\/_nuxt\//.test(html)) return "nuxt";
|
|
5466
|
+
if (/data-sveltekit-/.test(html)) return "sveltekit";
|
|
5467
|
+
if (/<meta\s+name="generator"\s+content="Astro/i.test(html)) return "astro";
|
|
5468
|
+
if (/<meta\s+name="generator"\s+content="WordPress/i.test(html)) return "wordpress";
|
|
5469
|
+
if (/\/build\/_assets\/.*\.js/.test(html) && /window\.__remixContext/.test(html)) return "remix";
|
|
5499
5470
|
}
|
|
5500
5471
|
return null;
|
|
5501
5472
|
};
|
|
@@ -5508,8 +5479,7 @@ var FRAMEWORK_DOCS = {
|
|
|
5508
5479
|
wordpress: "https://yoast.com/structured-data-with-schema-org-the-ultimate-guide/"
|
|
5509
5480
|
};
|
|
5510
5481
|
var frameworkHint = (fw) => {
|
|
5511
|
-
if (fw && FRAMEWORK_DOCS[fw])
|
|
5512
|
-
return ` See: ${FRAMEWORK_DOCS[fw]}`;
|
|
5482
|
+
if (fw && FRAMEWORK_DOCS[fw]) return ` See: ${FRAMEWORK_DOCS[fw]}`;
|
|
5513
5483
|
return " See: https://schema.org/docs/gs.html";
|
|
5514
5484
|
};
|
|
5515
5485
|
var structuredDataCoverage = {
|
|
@@ -5528,8 +5498,7 @@ var structuredDataCoverage = {
|
|
|
5528
5498
|
withStructuredData++;
|
|
5529
5499
|
for (const item of jsonLd) {
|
|
5530
5500
|
const type = item?.["@type"];
|
|
5531
|
-
if (typeof type === "string")
|
|
5532
|
-
types.push(type);
|
|
5501
|
+
if (typeof type === "string") types.push(type);
|
|
5533
5502
|
}
|
|
5534
5503
|
}
|
|
5535
5504
|
}
|
|
@@ -5606,8 +5575,7 @@ var citationWorthiness = {
|
|
|
5606
5575
|
signals.withLists++;
|
|
5607
5576
|
pageCitable = true;
|
|
5608
5577
|
}
|
|
5609
|
-
if (pageCitable)
|
|
5610
|
-
citablePages++;
|
|
5578
|
+
if (pageCitable) citablePages++;
|
|
5611
5579
|
}
|
|
5612
5580
|
if (citablePages >= pages.length * 0.7) {
|
|
5613
5581
|
return {
|
|
@@ -5640,9 +5608,10 @@ var topicalAuthoritySignals = {
|
|
|
5640
5608
|
const pages = ctx.sampledPages.slice(0, 10);
|
|
5641
5609
|
let totalInternalLinks = 0;
|
|
5642
5610
|
let pagesWithGoodLinking = 0;
|
|
5611
|
+
const resolveBase = ctx.mode === "local" ? ctx.baseUrl.href : ctx.baseUrl.origin;
|
|
5643
5612
|
for (const page of pages) {
|
|
5644
|
-
const links = extractLinks(page.html,
|
|
5645
|
-
const internalLinks = ctx.mode === "local" ? links.filter((l) =>
|
|
5613
|
+
const links = extractLinks(page.html, resolveBase);
|
|
5614
|
+
const internalLinks = ctx.mode === "local" ? links.filter((l) => l.startsWith("file:")) : links.filter((l) => {
|
|
5646
5615
|
try {
|
|
5647
5616
|
return new URL(l).origin === ctx.baseUrl.origin;
|
|
5648
5617
|
} catch {
|
|
@@ -5650,8 +5619,7 @@ var topicalAuthoritySignals = {
|
|
|
5650
5619
|
}
|
|
5651
5620
|
});
|
|
5652
5621
|
totalInternalLinks += internalLinks.length;
|
|
5653
|
-
if (internalLinks.length >= 3)
|
|
5654
|
-
pagesWithGoodLinking++;
|
|
5622
|
+
if (internalLinks.length >= 3) pagesWithGoodLinking++;
|
|
5655
5623
|
}
|
|
5656
5624
|
const avgLinks = pages.length > 0 ? Math.round(totalInternalLinks / pages.length) : 0;
|
|
5657
5625
|
if (avgLinks >= 5 && pagesWithGoodLinking >= pages.length * 0.7) {
|
|
@@ -5737,12 +5705,11 @@ var eeatSignals = {
|
|
|
5737
5705
|
return !!obj?.author;
|
|
5738
5706
|
});
|
|
5739
5707
|
const hasAuthorHtml = /class=["'][^"']*author[^"']*["']|rel=["']author["']/i.test(page.html);
|
|
5740
|
-
if (hasAuthorMeta || hasAuthorJsonLd || hasAuthorHtml)
|
|
5741
|
-
withAuthor++;
|
|
5708
|
+
if (hasAuthorMeta || hasAuthorJsonLd || hasAuthorHtml) withAuthor++;
|
|
5742
5709
|
const hasCredentials = /Ph\.?D|M\.?D|CPA|certified|licensed|expert|specialist/i.test(page.html);
|
|
5743
|
-
const
|
|
5744
|
-
|
|
5745
|
-
|
|
5710
|
+
const linkBase = ctx.mode === "local" ? ctx.baseUrl.href : ctx.baseUrl.origin;
|
|
5711
|
+
const hasAboutPage = extractLinks(page.html, linkBase).some((l) => /about|team|author/i.test(l));
|
|
5712
|
+
if (hasCredentials || hasAboutPage) withExpertise++;
|
|
5746
5713
|
}
|
|
5747
5714
|
const score = (withAuthor + withExpertise) / (pages.length * 2);
|
|
5748
5715
|
if (score >= 0.6) {
|
|
@@ -5782,8 +5749,7 @@ var faqSchema = {
|
|
|
5782
5749
|
return type === "FAQPage" || type === "QAPage";
|
|
5783
5750
|
});
|
|
5784
5751
|
const hasFaqHtml = /<details|<summary|class=["'][^"']*faq[^"']*["']|id=["'][^"']*faq[^"']*["']/i.test(page.html);
|
|
5785
|
-
if (hasFaqSchema || hasFaqHtml)
|
|
5786
|
-
withFaq++;
|
|
5752
|
+
if (hasFaqSchema || hasFaqHtml) withFaq++;
|
|
5787
5753
|
}
|
|
5788
5754
|
if (withFaq > 0) {
|
|
5789
5755
|
return {
|
|
@@ -5813,6 +5779,15 @@ var canonicalUrlConsistency = {
|
|
|
5813
5779
|
description: "Checks if pages have consistent canonical URLs",
|
|
5814
5780
|
weight: 0.5,
|
|
5815
5781
|
run: async (ctx) => {
|
|
5782
|
+
if (ctx.mode === "local") {
|
|
5783
|
+
return {
|
|
5784
|
+
id: "canonical-url-consistency",
|
|
5785
|
+
name: "Canonical URL Consistency",
|
|
5786
|
+
category: "geo-signals",
|
|
5787
|
+
status: "info",
|
|
5788
|
+
message: "only meaningful for live urls. re-run against a deployed site to verify"
|
|
5789
|
+
};
|
|
5790
|
+
}
|
|
5816
5791
|
const pages = ctx.sampledPages.slice(0, 10);
|
|
5817
5792
|
let withCanonical = 0;
|
|
5818
5793
|
let selfReferencing = 0;
|
|
@@ -5869,8 +5844,6 @@ var geoSignalChecks = [
|
|
|
5869
5844
|
faqSchema,
|
|
5870
5845
|
canonicalUrlConsistency
|
|
5871
5846
|
];
|
|
5872
|
-
|
|
5873
|
-
// node_modules/@agentimization/core/dist/checks/agent-protocols.js
|
|
5874
5847
|
var mcpServerCard = {
|
|
5875
5848
|
id: "mcp-server-card",
|
|
5876
5849
|
name: "MCP Server Card",
|
|
@@ -6004,12 +5977,9 @@ var contentSignals = {
|
|
|
6004
5977
|
}
|
|
6005
5978
|
const text = ctx.robotsTxt.toLowerCase();
|
|
6006
5979
|
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");
|
|
5980
|
+
if (/ai[-_]?train/i.test(ctx.robotsTxt)) signals.push("ai-train");
|
|
5981
|
+
if (/ai[-_]?input/i.test(ctx.robotsTxt)) signals.push("ai-input");
|
|
5982
|
+
if (/content[-_]?signals?/i.test(ctx.robotsTxt)) signals.push("content-signals");
|
|
6013
5983
|
const hasGPTBot = text.includes("gptbot");
|
|
6014
5984
|
const hasClaudeBot = text.includes("claudebot") || text.includes("claude-web");
|
|
6015
5985
|
const hasGoogleExtended = text.includes("google-extended");
|
|
@@ -6244,8 +6214,6 @@ var agentProtocolChecks = [
|
|
|
6244
6214
|
agentSkillsIndex,
|
|
6245
6215
|
agentsMd
|
|
6246
6216
|
];
|
|
6247
|
-
|
|
6248
|
-
// node_modules/@agentimization/core/dist/checks/index.js
|
|
6249
6217
|
var ALL_CHECKS = [
|
|
6250
6218
|
...contentDiscoverabilityChecks,
|
|
6251
6219
|
...markdownAvailabilityChecks,
|
|
@@ -6256,13 +6224,31 @@ var ALL_CHECKS = [
|
|
|
6256
6224
|
...geoSignalChecks,
|
|
6257
6225
|
...agentProtocolChecks
|
|
6258
6226
|
];
|
|
6259
|
-
|
|
6260
|
-
|
|
6261
|
-
|
|
6262
|
-
|
|
6227
|
+
var readIfExists = (path) => {
|
|
6228
|
+
try {
|
|
6229
|
+
if (existsSync(path)) {
|
|
6230
|
+
return readFileSync(path, "utf-8");
|
|
6231
|
+
}
|
|
6232
|
+
} catch {
|
|
6233
|
+
}
|
|
6234
|
+
return void 0;
|
|
6235
|
+
};
|
|
6236
|
+
var findUpward = (start, names, maxDepth = 6) => {
|
|
6237
|
+
let current = start;
|
|
6238
|
+
for (let i = 0; i < maxDepth; i++) {
|
|
6239
|
+
for (const name of names) {
|
|
6240
|
+
const candidate = join(current, name);
|
|
6241
|
+
const value = readIfExists(candidate);
|
|
6242
|
+
if (value !== void 0) return value;
|
|
6243
|
+
}
|
|
6244
|
+
const parent = dirname(current);
|
|
6245
|
+
if (parent === current) break;
|
|
6246
|
+
current = parent;
|
|
6247
|
+
}
|
|
6248
|
+
return void 0;
|
|
6249
|
+
};
|
|
6263
6250
|
var walkDir = (dir, extensions, maxDepth = 10) => {
|
|
6264
|
-
if (maxDepth <= 0)
|
|
6265
|
-
return [];
|
|
6251
|
+
if (maxDepth <= 0) return [];
|
|
6266
6252
|
const results = [];
|
|
6267
6253
|
try {
|
|
6268
6254
|
const entries = readdirSync(dir, { withFileTypes: true });
|
|
@@ -6281,15 +6267,6 @@ var walkDir = (dir, extensions, maxDepth = 10) => {
|
|
|
6281
6267
|
}
|
|
6282
6268
|
return results;
|
|
6283
6269
|
};
|
|
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
6270
|
var buildLocalContext = (dirPath, config) => {
|
|
6294
6271
|
const baseUrl = new URL(`file://${dirPath}`);
|
|
6295
6272
|
const robotsTxt = readIfExists(join(dirPath, "robots.txt"));
|
|
@@ -6299,7 +6276,7 @@ var buildLocalContext = (dirPath, config) => {
|
|
|
6299
6276
|
const mcpServerCard2 = readIfExists(join(dirPath, ".well-known", "mcp", "server-card.json"));
|
|
6300
6277
|
const apiCatalog2 = readIfExists(join(dirPath, ".well-known", "api-catalog"));
|
|
6301
6278
|
const agentSkillsIndex2 = readIfExists(join(dirPath, ".well-known", "agent-skills", "index.json"));
|
|
6302
|
-
const agentsMd2 =
|
|
6279
|
+
const agentsMd2 = findUpward(dirPath, ["AGENTS.md", "AGENT.md"]);
|
|
6303
6280
|
const sitemapUrls = sitemapXml ? parseSitemapUrls(sitemapXml) : [];
|
|
6304
6281
|
if (!sitemapXml && robotsTxt) {
|
|
6305
6282
|
const sitemapMatch = robotsTxt.match(/Sitemap:\s*(.+)/i);
|
|
@@ -6316,8 +6293,8 @@ var buildLocalContext = (dirPath, config) => {
|
|
|
6316
6293
|
}
|
|
6317
6294
|
const htmlFiles = walkDir(dirPath, /* @__PURE__ */ new Set([".html", ".htm"]));
|
|
6318
6295
|
const mdFiles = walkDir(dirPath, /* @__PURE__ */ new Set([".md", ".mdx"]));
|
|
6319
|
-
const
|
|
6320
|
-
const sampled =
|
|
6296
|
+
const sampleSource = htmlFiles.length > 0 ? htmlFiles : mdFiles;
|
|
6297
|
+
const sampled = sampleSource.slice(0, config.sampleSize);
|
|
6321
6298
|
const sampledPages = sampled.map((filePath) => {
|
|
6322
6299
|
const content = readFileSync(filePath, "utf-8");
|
|
6323
6300
|
const relPath = relative(dirPath, filePath);
|
|
@@ -6368,19 +6345,12 @@ var wrapMarkdownAsHtml = (md, title) => {
|
|
|
6368
6345
|
html = html.replace(/^([^<\n].+)$/gm, "<p>$1</p>");
|
|
6369
6346
|
return `<!DOCTYPE html><html><head><title>${title}</title></head><body><main>${html}</main></body></html>`;
|
|
6370
6347
|
};
|
|
6371
|
-
|
|
6372
|
-
// node_modules/@agentimization/core/dist/index.js
|
|
6373
6348
|
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";
|
|
6349
|
+
if (score >= 95) return "A+";
|
|
6350
|
+
if (score >= 85) return "A";
|
|
6351
|
+
if (score >= 70) return "B";
|
|
6352
|
+
if (score >= 55) return "C";
|
|
6353
|
+
if (score >= 40) return "D";
|
|
6384
6354
|
return "F";
|
|
6385
6355
|
};
|
|
6386
6356
|
var buildRemoteContext = async (targetUrl, config) => {
|
|
@@ -6389,7 +6359,15 @@ var buildRemoteContext = async (targetUrl, config) => {
|
|
|
6389
6359
|
emit({ type: "phase", phase: "fetching" });
|
|
6390
6360
|
const baseUrl = new URL(targetUrl);
|
|
6391
6361
|
const origin = baseUrl.origin;
|
|
6392
|
-
const [
|
|
6362
|
+
const [
|
|
6363
|
+
robotsResult,
|
|
6364
|
+
llmsResult,
|
|
6365
|
+
llmsFullResult,
|
|
6366
|
+
sitemapResult,
|
|
6367
|
+
mcpCardResult,
|
|
6368
|
+
apiCatalogResult,
|
|
6369
|
+
agentSkillsResult
|
|
6370
|
+
] = await Promise.allSettled([
|
|
6393
6371
|
fetchText(`${origin}/robots.txt`, config),
|
|
6394
6372
|
fetchText(`${origin}/llms.txt`, config),
|
|
6395
6373
|
fetchText(`${origin}/llms-full.txt`, config),
|
|
@@ -6523,7 +6501,9 @@ var runAudit = async (ctx, config, start) => {
|
|
|
6523
6501
|
overallScore = 50;
|
|
6524
6502
|
}
|
|
6525
6503
|
const categories = {};
|
|
6526
|
-
const activeCategories = config.categories.filter(
|
|
6504
|
+
const activeCategories = config.categories.filter(
|
|
6505
|
+
(cat) => results.some((r) => r.category === cat)
|
|
6506
|
+
);
|
|
6527
6507
|
for (const cat of activeCategories) {
|
|
6528
6508
|
const catResults = results.filter((r) => r.category === cat);
|
|
6529
6509
|
const catScorable = catResults.filter((r) => r.status !== "skip" && r.status !== "info");
|
|
@@ -7230,11 +7210,11 @@ var copyToClipboard = async (text) => {
|
|
|
7230
7210
|
// src/ui/action-menu.tsx
|
|
7231
7211
|
import { writeFileSync } from "fs";
|
|
7232
7212
|
import { resolve } from "path";
|
|
7233
|
-
import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
7213
|
+
import { Fragment as Fragment2, jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
7234
7214
|
var FEEDBACK_OK = "oklch(0.858 0.109 142.7)";
|
|
7235
7215
|
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." },
|
|
7216
|
+
var buildMenuOptions = (hasIssues) => [
|
|
7217
|
+
...hasIssues ? [{ label: "Copy fix prompt to clipboard", value: "copy", hint: "paste into Claude, ChatGPT, etc." }] : [],
|
|
7238
7218
|
{ label: "Save JSON report", value: "json", hint: "agentimization-report.json" },
|
|
7239
7219
|
{ label: "Run another URL or path", value: "retry" },
|
|
7240
7220
|
{ label: "Exit", value: "exit" }
|
|
@@ -7251,6 +7231,8 @@ var ActionMenu = ({
|
|
|
7251
7231
|
const [feedback, setFeedback] = useState2(null);
|
|
7252
7232
|
const [input, setInput] = useState2("");
|
|
7253
7233
|
const [done, setDone] = useState2(false);
|
|
7234
|
+
const hasIssues = result.summary.failed > 0 || result.summary.warned > 0;
|
|
7235
|
+
const menuOptions = buildMenuOptions(hasIssues);
|
|
7254
7236
|
const handleAction = (action) => {
|
|
7255
7237
|
const promptOpts = { mode: isLocal ? "local" : "remote", target };
|
|
7256
7238
|
switch (action) {
|
|
@@ -7285,6 +7267,7 @@ var ActionMenu = ({
|
|
|
7285
7267
|
setDone(true);
|
|
7286
7268
|
setTimeout(() => {
|
|
7287
7269
|
exit();
|
|
7270
|
+
process.stdout.write("\n");
|
|
7288
7271
|
if (result.overall_score < 50) process.exit(1);
|
|
7289
7272
|
}, 50);
|
|
7290
7273
|
break;
|
|
@@ -7297,13 +7280,16 @@ var ActionMenu = ({
|
|
|
7297
7280
|
if (key.upArrow) {
|
|
7298
7281
|
setSelected((prev) => Math.max(0, prev - 1));
|
|
7299
7282
|
} else if (key.downArrow) {
|
|
7300
|
-
setSelected((prev) => Math.min(
|
|
7283
|
+
setSelected((prev) => Math.min(menuOptions.length - 1, prev + 1));
|
|
7301
7284
|
} else if (key.return) {
|
|
7302
|
-
const option =
|
|
7285
|
+
const option = menuOptions[selected];
|
|
7303
7286
|
handleAction(option.value);
|
|
7304
7287
|
} else if (char === "q" || key.escape) {
|
|
7305
7288
|
setDone(true);
|
|
7306
|
-
setTimeout(() =>
|
|
7289
|
+
setTimeout(() => {
|
|
7290
|
+
exit();
|
|
7291
|
+
process.stdout.write("\n");
|
|
7292
|
+
}, 50);
|
|
7307
7293
|
}
|
|
7308
7294
|
return;
|
|
7309
7295
|
}
|
|
@@ -7337,7 +7323,7 @@ var ActionMenu = ({
|
|
|
7337
7323
|
return /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", marginTop: 1, children: [
|
|
7338
7324
|
/* @__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
7325
|
/* @__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:
|
|
7326
|
+
/* @__PURE__ */ jsx4(Box4, { flexDirection: "column", marginTop: 1, children: menuOptions.map((opt, i) => {
|
|
7341
7327
|
const isSelected = i === selected;
|
|
7342
7328
|
return /* @__PURE__ */ jsxs4(Box4, { paddingLeft: 2, children: [
|
|
7343
7329
|
/* @__PURE__ */ jsxs4(Text4, { color: isSelected ? toInkColor(ACCENT_BLUE) : void 0, children: [
|
|
@@ -7352,7 +7338,10 @@ var ActionMenu = ({
|
|
|
7352
7338
|
] }) : null
|
|
7353
7339
|
] }, opt.value);
|
|
7354
7340
|
}) }),
|
|
7355
|
-
feedback ? /* @__PURE__ */
|
|
7341
|
+
feedback ? /* @__PURE__ */ jsxs4(Fragment2, { children: [
|
|
7342
|
+
/* @__PURE__ */ jsx4(Box4, { marginTop: 1, paddingLeft: 2, children: /* @__PURE__ */ jsx4(Text4, { color: toInkColor(feedback.startsWith("\u2713") ? FEEDBACK_OK : FEEDBACK_ERR), children: feedback }) }),
|
|
7343
|
+
/* @__PURE__ */ jsx4(Box4, { height: 1, children: /* @__PURE__ */ jsx4(Text4, { children: " " }) })
|
|
7344
|
+
] }) : null
|
|
7356
7345
|
] });
|
|
7357
7346
|
};
|
|
7358
7347
|
|
|
@@ -7433,7 +7422,7 @@ var ErrorActions = ({ onRetry }) => {
|
|
|
7433
7422
|
|
|
7434
7423
|
// src/ui/target.ts
|
|
7435
7424
|
import { existsSync as existsSync2, statSync } from "fs";
|
|
7436
|
-
import { resolve as resolve2 } from "path";
|
|
7425
|
+
import { join as join2, resolve as resolve2 } from "path";
|
|
7437
7426
|
var isLocalPath = (arg) => {
|
|
7438
7427
|
if (arg === "." || arg === ".." || arg.startsWith("./") || arg.startsWith("../") || arg.startsWith("/")) {
|
|
7439
7428
|
return true;
|
|
@@ -7449,11 +7438,30 @@ var normalizeUrl = (arg) => {
|
|
|
7449
7438
|
const parsed = new URL(arg.startsWith("http") ? arg : `https://${arg}`);
|
|
7450
7439
|
return parsed.href;
|
|
7451
7440
|
};
|
|
7441
|
+
var BUILD_DIR_NAMES = ["dist", "build", "out", "_site", "public"];
|
|
7442
|
+
var hasIndex = (dir) => ["index.html", "index.htm", "index.md"].some((name) => existsSync2(join2(dir, name)));
|
|
7443
|
+
var findBuildDir = (root) => {
|
|
7444
|
+
for (const name of BUILD_DIR_NAMES) {
|
|
7445
|
+
const candidate = join2(root, name);
|
|
7446
|
+
if (existsSync2(candidate) && statSync(candidate).isDirectory() && hasIndex(candidate)) {
|
|
7447
|
+
return candidate;
|
|
7448
|
+
}
|
|
7449
|
+
}
|
|
7450
|
+
return void 0;
|
|
7451
|
+
};
|
|
7452
7452
|
var resolveTarget = (input) => {
|
|
7453
7453
|
const trimmed = input.trim();
|
|
7454
7454
|
if (!trimmed) return { error: "Please enter a URL or path." };
|
|
7455
7455
|
if (isLocalPath(trimmed)) {
|
|
7456
|
-
|
|
7456
|
+
const requested = resolve2(trimmed);
|
|
7457
|
+
if (hasIndex(requested)) {
|
|
7458
|
+
return { target: requested, isLocal: true };
|
|
7459
|
+
}
|
|
7460
|
+
const buildDir = findBuildDir(requested);
|
|
7461
|
+
if (buildDir) {
|
|
7462
|
+
return { target: buildDir, isLocal: true, autoDetectedFrom: requested };
|
|
7463
|
+
}
|
|
7464
|
+
return { target: requested, isLocal: true };
|
|
7457
7465
|
}
|
|
7458
7466
|
try {
|
|
7459
7467
|
return { target: normalizeUrl(trimmed), isLocal: false };
|
|
@@ -7464,7 +7472,13 @@ var resolveTarget = (input) => {
|
|
|
7464
7472
|
|
|
7465
7473
|
// src/ui/app.tsx
|
|
7466
7474
|
import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
7467
|
-
var App = ({
|
|
7475
|
+
var App = ({
|
|
7476
|
+
target: initialTarget,
|
|
7477
|
+
isLocal: initialIsLocal,
|
|
7478
|
+
categories,
|
|
7479
|
+
sampleSize,
|
|
7480
|
+
autoDetectedFrom
|
|
7481
|
+
}) => {
|
|
7468
7482
|
const [target, setTarget] = useState4(initialTarget);
|
|
7469
7483
|
const [isLocal, setIsLocal] = useState4(initialIsLocal);
|
|
7470
7484
|
const [phase, setPhase] = useState4("init");
|
|
@@ -7573,6 +7587,11 @@ var App = ({ target: initialTarget, isLocal: initialIsLocal, categories, sampleS
|
|
|
7573
7587
|
gradeColor: result ? GRADE_COLORS[result.grade] : void 0
|
|
7574
7588
|
}
|
|
7575
7589
|
) }),
|
|
7590
|
+
autoDetectedFrom ? /* @__PURE__ */ jsxs6(Text6, { dimColor: true, children: [
|
|
7591
|
+
"auto-detected build output (was ",
|
|
7592
|
+
autoDetectedFrom,
|
|
7593
|
+
")"
|
|
7594
|
+
] }) : null,
|
|
7576
7595
|
isLocal && networkSkipped > 0 && phase === "done" ? /* @__PURE__ */ jsxs6(Text6, { dimColor: true, children: [
|
|
7577
7596
|
networkSkipped,
|
|
7578
7597
|
" network-only checks skipped"
|
|
@@ -7636,10 +7655,18 @@ Examples:
|
|
|
7636
7655
|
$ agentimization ./docs --category content-structure
|
|
7637
7656
|
$ agentimization ./public --md | pbcopy`).action(async (target, opts) => {
|
|
7638
7657
|
const categories = opts.category ? [opts.category] : void 0;
|
|
7639
|
-
const
|
|
7658
|
+
const resolved = resolveTarget(target);
|
|
7659
|
+
if ("error" in resolved) {
|
|
7660
|
+
console.error(`Error: ${resolved.error}`);
|
|
7661
|
+
return process.exit(1);
|
|
7662
|
+
}
|
|
7663
|
+
const { target: targetResolved, isLocal, autoDetectedFrom } = resolved;
|
|
7664
|
+
if (autoDetectedFrom && !opts.json) {
|
|
7665
|
+
console.error(`auditing built output at ${targetResolved}`);
|
|
7666
|
+
}
|
|
7640
7667
|
if (opts.json) {
|
|
7641
7668
|
try {
|
|
7642
|
-
const result = isLocal ? await auditLocal(
|
|
7669
|
+
const result = isLocal ? await auditLocal(targetResolved, { categories, sampleSize: opts.sampleSize }) : await audit(targetResolved, { categories, sampleSize: opts.sampleSize });
|
|
7643
7670
|
console.log(JSON.stringify(result, null, 2));
|
|
7644
7671
|
if (result.overall_score < 50) process.exit(1);
|
|
7645
7672
|
} catch (error) {
|
|
@@ -7651,8 +7678,8 @@ Examples:
|
|
|
7651
7678
|
}
|
|
7652
7679
|
if (opts.md) {
|
|
7653
7680
|
try {
|
|
7654
|
-
const result = isLocal ? await auditLocal(
|
|
7655
|
-
console.log(generateAgentPrompt(result, { mode: isLocal ? "local" : "remote", target }));
|
|
7681
|
+
const result = isLocal ? await auditLocal(targetResolved, { categories, sampleSize: opts.sampleSize }) : await audit(targetResolved, { categories, sampleSize: opts.sampleSize });
|
|
7682
|
+
console.log(generateAgentPrompt(result, { mode: isLocal ? "local" : "remote", target: targetResolved }));
|
|
7656
7683
|
if (result.overall_score < 50) process.exit(1);
|
|
7657
7684
|
} catch (error) {
|
|
7658
7685
|
const msg = error instanceof Error ? error.message : "Unknown error";
|
|
@@ -7661,24 +7688,16 @@ Examples:
|
|
|
7661
7688
|
}
|
|
7662
7689
|
return;
|
|
7663
7690
|
}
|
|
7664
|
-
const targetResolved = isLocal ? resolve3(target) : normalizeUrl2(target);
|
|
7665
7691
|
render(
|
|
7666
7692
|
React2.createElement(App, {
|
|
7667
7693
|
target: targetResolved,
|
|
7668
7694
|
isLocal,
|
|
7669
7695
|
categories,
|
|
7670
|
-
sampleSize: opts.sampleSize
|
|
7696
|
+
sampleSize: opts.sampleSize,
|
|
7697
|
+
autoDetectedFrom
|
|
7671
7698
|
})
|
|
7672
7699
|
);
|
|
7673
7700
|
});
|
|
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
7701
|
try {
|
|
7683
7702
|
program.parse();
|
|
7684
7703
|
} catch (err) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agentimization",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "GEO audit CLI — check if your website is agent-ready",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Anthony Lio <hello@antl.io>",
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
"dev": "tsup --watch",
|
|
41
41
|
"typecheck": "tsc --noEmit",
|
|
42
42
|
"test": "vitest run",
|
|
43
|
-
"prepublishOnly": "bun run build && cp ../../README.md ./README.md && cp ../../LICENSE ./LICENSE"
|
|
43
|
+
"prepublishOnly": "bun run build && cp -f ../../README.md ./README.md && cp -f ../../LICENSE ./LICENSE"
|
|
44
44
|
},
|
|
45
45
|
"dependencies": {
|
|
46
46
|
"commander": "^14.0.3",
|