@eternify-labs/seo-audit 0.1.0 → 0.3.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 +75 -10
- package/THIRD-PARTY-NOTICES.md +40 -0
- package/dist/aiAudit-4VRLOX3Q.js +2 -0
- package/dist/aiAudit-5EL5LJKD.js +2 -0
- package/dist/aiAudit-CPECFC7E.js +2 -0
- package/dist/aiAudit-DRZEPNLG.js +2 -0
- package/dist/aiAudit-GK2PWFLY.js +2 -0
- package/dist/aiAudit-HNBKV2CD.js +2 -0
- package/dist/aiAudit-LARDDH2P.js +2 -0
- package/dist/aiAudit-LJDIXAE5.js +2 -0
- package/dist/aiAudit-PJJH2G52.js +2 -0
- package/dist/aiAudit-Q7XF6GGP.js +2 -0
- package/dist/aiAudit-U4INAUJS.js +2 -0
- package/dist/aiAudit-WHUM7EJL.js +2 -0
- package/dist/aiAudit-YXGRX7CV.js +2 -0
- package/dist/chunk-2FHZ2BFC.js +16 -0
- package/dist/chunk-3ASHCSJG.js +2 -0
- package/dist/chunk-4AINMOLH.js +10 -0
- package/dist/chunk-4LCUBQUE.js +2 -0
- package/dist/chunk-4NKZE24G.js +2 -0
- package/dist/chunk-5HL5QNMH.js +10 -0
- package/dist/chunk-6K6Q77LY.js +50 -0
- package/dist/chunk-7TI3OPOC.js +16 -0
- package/dist/chunk-AC6ELEHI.js +42 -0
- package/dist/chunk-AIOAF3VX.js +2 -0
- package/dist/chunk-ARTOQXQP.js +42 -0
- package/dist/chunk-BSOQNREQ.js +2 -0
- package/dist/chunk-BT2ILFUJ.js +11 -0
- package/dist/chunk-CDCS5KKM.js +37 -0
- package/dist/chunk-CU3RA4ZL.js +37 -0
- package/dist/chunk-CWDXO6A5.js +37 -0
- package/dist/chunk-DUM3VDXO.js +16 -0
- package/dist/chunk-EYZZ47FM.js +31 -0
- package/dist/chunk-G3WWINTJ.js +10 -0
- package/dist/chunk-GKGSIXNZ.js +42 -0
- package/dist/chunk-JOJ27B65.js +19 -0
- package/dist/chunk-KM6WE6OO.js +37 -0
- package/dist/chunk-KUW3W6JN.js +2 -0
- package/dist/chunk-NJ4T52KL.js +2 -0
- package/dist/chunk-O3BX4SMT.js +16 -0
- package/dist/chunk-OSBRK5KC.js +50 -0
- package/dist/chunk-SPX2RA5T.js +2 -0
- package/dist/chunk-TZUYZGAN.js +16 -0
- package/dist/chunk-U3KERC3T.js +2 -0
- package/dist/chunk-UW3BLXEX.js +2 -0
- package/dist/chunk-UY6CGWIN.js +2 -0
- package/dist/chunk-V6QVPBUT.js +2 -0
- package/dist/chunk-WO7RE3KE.js +50 -0
- package/dist/chunk-WQF3EL6I.js +37 -0
- package/dist/chunk-X7TFHEYT.js +10 -0
- package/dist/chunk-XK62BKWH.js +3 -0
- package/dist/chunk-YPIZMUWS.js +16 -0
- package/dist/cli.js +142 -26
- package/dist/crawler-S3FAQ6PB.js +2 -0
- package/dist/drift-WE75HT5C.js +2 -0
- package/dist/report-5GRFH2IG.js +2 -0
- package/dist/report-BT7UL6W3.js +2 -0
- package/dist/report-DTYZQFUQ.js +2 -0
- package/dist/report-FQHYD5MV.js +2 -0
- package/dist/report-KQZTM623.js +2 -0
- package/dist/report-OTFGRNMJ.js +2 -0
- package/dist/report-UNRVFXFM.js +2 -0
- package/dist/report-VEF6N4W2.js +2 -0
- package/dist/report-VNSGKYVL.js +2 -0
- package/dist/report-YZLGDZH6.js +2 -0
- package/dist/store-I6ZAS3QR.js +2 -0
- package/dist/store-W5O642L3.js +2 -0
- package/dist/ui/assets/index-CFVxJXmH.css +1 -0
- package/dist/ui/assets/index-Cmt-9HyO.js +67 -0
- package/dist/ui/index.html +2 -2
- package/package.json +9 -4
- package/dist/ui/assets/index-C8yCj8Iz.css +0 -1
- package/dist/ui/assets/index-DB7HIYjL.js +0 -67
package/README.md
CHANGED
|
@@ -9,15 +9,50 @@ npx @eternify-labs/seo-audit
|
|
|
9
9
|
```
|
|
10
10
|
|
|
11
11
|
First run walks through a Claude Code–style setup: pick a provider (Claude via
|
|
12
|
-
your Claude Code subscription login,
|
|
13
|
-
pick a model, verify the connection —
|
|
14
|
-
`http://localhost:4173`.
|
|
12
|
+
your Claude Code subscription login, OpenAI via ChatGPT sign-in / API key, or
|
|
13
|
+
Gemini via Google sign-in / API key), pick a model, verify the connection —
|
|
14
|
+
then the web UI opens at `http://localhost:4173`.
|
|
15
|
+
|
|
16
|
+
Under the hood every AI call runs through the **real Claude Code runtime**
|
|
17
|
+
(bundled by the Claude Agent SDK), the **real Codex binary** (bundled by the
|
|
18
|
+
Codex SDK), or the **real Gemini CLI** (bundled npm dependency, driven in
|
|
19
|
+
headless mode) — the same engines behind the `claude`, `codex`, and `gemini`
|
|
20
|
+
CLIs. This tool orchestrates them; it does not reimplement their agent loops.
|
|
21
|
+
|
|
22
|
+
## Prerequisites
|
|
23
|
+
|
|
24
|
+
**Required**
|
|
25
|
+
|
|
26
|
+
- **Node.js ≥ 20** (`node --version`)
|
|
27
|
+
- **An AI account**, one of:
|
|
28
|
+
- *Claude (recommended)* — a Claude subscription with Claude Code logged in
|
|
29
|
+
on this machine (run `claude` once and sign in), **or** an
|
|
30
|
+
`ANTHROPIC_API_KEY`
|
|
31
|
+
- *OpenAI* — a ChatGPT subscription (sign-in opens in your browser during
|
|
32
|
+
setup; the codex binary is bundled, no install needed), **or** an
|
|
33
|
+
`OPENAI_API_KEY`
|
|
34
|
+
- *Gemini* — a Google account (free tier; Google AI Pro raises limits —
|
|
35
|
+
sign-in runs through the bundled Gemini CLI during setup), **or** a
|
|
36
|
+
`GEMINI_API_KEY` from AI Studio
|
|
37
|
+
|
|
38
|
+
**Feature-specific**
|
|
39
|
+
|
|
40
|
+
- **git** — required for codebase auto-fix (and the target repo's working
|
|
41
|
+
tree must be clean, so every AI change stays revertible)
|
|
42
|
+
- **Google Chrome or Edge** — only if you enable JS rendering for crawls
|
|
43
|
+
- **Google API key** (free) — recommended for the Performance category; the
|
|
44
|
+
keyless PageSpeed quota is shared globally and usually exhausted
|
|
45
|
+
- **Google OAuth Desktop client** (free) — only for the Search Console
|
|
46
|
+
integration (Settings walks you through it)
|
|
47
|
+
- **DataForSEO account** (paid) — only for real keyword search volumes
|
|
48
|
+
|
|
49
|
+
Works on macOS, Linux, and Windows (bundled binaries cover x64 + arm64).
|
|
15
50
|
|
|
16
51
|
## Features
|
|
17
52
|
|
|
18
53
|
Every audit runs in **two rounds**:
|
|
19
54
|
|
|
20
|
-
1. **Rules** — a deterministic engine checks
|
|
55
|
+
1. **Rules** — a deterministic engine checks 60+ measurable facts (titles,
|
|
21
56
|
canonicals, structured data, robots/sitemap, llms.txt, AI-crawler access,
|
|
22
57
|
alt text, rendering strategy…). Reproducible, evidence-backed, free.
|
|
23
58
|
2. **AI auditor** — the model then audits like an expert consultant: for
|
|
@@ -31,13 +66,21 @@ Every audit runs in **two rounds**:
|
|
|
31
66
|
- **Domain Audit** — crawl (optionally JS-rendered via headless Chrome) →
|
|
32
67
|
two-round findings + Core Web Vitals (PageSpeed Insights/CrUX) → five-category
|
|
33
68
|
scorecard + prioritized action plan.
|
|
34
|
-
- **Keywords** — coverage/gap/intent analysis
|
|
35
|
-
|
|
36
|
-
connected)
|
|
69
|
+
- **Keywords** — coverage/gap/intent analysis with buyer-journey stages and
|
|
70
|
+
**AI query fan-out coverage**; enriched with **real Search Console queries**
|
|
71
|
+
(when connected), **real search volumes** (DataForSEO, optional), and
|
|
72
|
+
opt-in **SERP fit** analysis (the AI web-searches each keyword and flags
|
|
73
|
+
page-type mismatches against what actually ranks — no paid API).
|
|
37
74
|
- **AI Visibility** — prompt tracking: asks your model the questions real
|
|
38
75
|
users ask, measures whether your site is the answer (primary / mentioned /
|
|
39
76
|
absent), scores visibility 0–100, and shows which competitors the AI
|
|
40
77
|
recommends instead.
|
|
78
|
+
- **Content Briefs** — the AI researches the live SERP for a keyword, scores
|
|
79
|
+
the ranking competitors, and produces an executable brief anchored on
|
|
80
|
+
Information Gain, with internal links verified against your crawl.
|
|
81
|
+
- **Monitor** — compare audit runs over time and catch SEO regressions
|
|
82
|
+
(canonical moved, noindex added, schema stripped, content shrank) with
|
|
83
|
+
severity-classified before/after diffs.
|
|
41
84
|
- **Codebase** — two-round scan of any local repo, then **auto-fix**: an AI
|
|
42
85
|
agent edits the code, every change is shown as a git diff, and any fix can
|
|
43
86
|
be reverted with one click. Requires a clean git tree so everything stays
|
|
@@ -60,6 +103,8 @@ eternify-seo # start the web UI (default)
|
|
|
60
103
|
eternify-seo login # (re)configure provider / auth / model
|
|
61
104
|
eternify-seo audit <domain> # headless domain audit (--pages N, --no-ai, --json)
|
|
62
105
|
eternify-seo scan [path] # headless codebase audit (--no-ai, --json)
|
|
106
|
+
eternify-seo drift <domain> # re-audit + compare vs previous run; exit 1 on
|
|
107
|
+
# regressions (--fail-on critical|warning|info)
|
|
63
108
|
eternify-seo config # show current configuration
|
|
64
109
|
```
|
|
65
110
|
|
|
@@ -67,9 +112,24 @@ Scores come from one penalty engine applied to both rounds' findings — the AI
|
|
|
67
112
|
contributes by *finding issues with evidence*, never by inventing numbers.
|
|
68
113
|
With `--no-ai` only round 1 runs and the score is purely rule-based.
|
|
69
114
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
115
|
+
### Catch SEO regressions in CI
|
|
116
|
+
|
|
117
|
+
```yaml
|
|
118
|
+
# .github/workflows/seo-drift.yml — fails the deploy if it broke SEO
|
|
119
|
+
- run: npx @eternify-labs/seo-audit drift yourdomain.com --fail-on warning
|
|
120
|
+
env: { ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY } } }
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
Or in cron for scheduled monitoring: `0 6 * * * eternify-seo drift yourdomain.com`
|
|
124
|
+
|
|
125
|
+
Config lives in `~/.eternify/config.json` (chmod 600); reports and caches in
|
|
126
|
+
a local SQLite database at `~/.eternify/data.db`. Everything runs locally —
|
|
127
|
+
page content is sent only to the AI provider you chose.
|
|
128
|
+
|
|
129
|
+
**Economy mode** (default on): mechanical tasks — visibility classification,
|
|
130
|
+
the dissenter lens, paste tests, SERP-type classification — automatically run
|
|
131
|
+
on a lighter, cheaper model (Haiku 4.5 / Gemini Flash), while audits, briefs
|
|
132
|
+
and reports keep your selected model. Toggle in Settings.
|
|
73
133
|
|
|
74
134
|
## Development (Eternify Labs internal)
|
|
75
135
|
|
|
@@ -78,8 +138,13 @@ npm install
|
|
|
78
138
|
npm run dev # CLI + server (tsx)
|
|
79
139
|
npm run dev:ui # Vite dev server for the UI (proxies /api to :4173)
|
|
80
140
|
npm test # rule-engine unit tests (vitest)
|
|
141
|
+
npm run evals # real-model prompt-quality gates (run after prompt changes)
|
|
81
142
|
npm run build # dist/cli.js (minified) + dist/ui/
|
|
82
143
|
```
|
|
83
144
|
|
|
84
145
|
Publishing ships **only** the minified `dist/` (see `files` in package.json) —
|
|
85
146
|
source stays in this private monorepo.
|
|
147
|
+
|
|
148
|
+
---
|
|
149
|
+
|
|
150
|
+
Built by [Krutik Parikh](https://www.krutikparikh.ca/) at [Eternify Labs](https://eternifylabs.com/).
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# Third-party notices
|
|
2
|
+
|
|
3
|
+
This product adapts concepts, checklists, and methodologies from the following
|
|
4
|
+
MIT-licensed projects. Per the MIT license, their copyright and permission
|
|
5
|
+
notices are reproduced below.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
**opencode** — https://github.com/anomalyco/opencode
|
|
10
|
+
Copyright (c) 2025 opencode
|
|
11
|
+
|
|
12
|
+
**claude-seo** — https://github.com/AgriciDaniel/claude-seo
|
|
13
|
+
Copyright (c) 2026 agricidaniel
|
|
14
|
+
|
|
15
|
+
**Marketing Skills** — https://github.com/coreyhaines31/marketingskills
|
|
16
|
+
Copyright (c) 2025 Corey Haines
|
|
17
|
+
|
|
18
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
19
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
20
|
+
in the Software without restriction, including without limitation the rights
|
|
21
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
22
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
23
|
+
furnished to do so, subject to the following conditions:
|
|
24
|
+
|
|
25
|
+
The above copyright notice and this permission notice shall be included in all
|
|
26
|
+
copies or substantial portions of the Software.
|
|
27
|
+
|
|
28
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
29
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
30
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
31
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
32
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
33
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
34
|
+
SOFTWARE.
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
Statistics surfaced in reports cite their original research sources
|
|
39
|
+
(Princeton GEO / KDD 2024, SE Ranking, Ahrefs, and others), not these
|
|
40
|
+
repositories.
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import{c as i}from"./chunk-3ASHCSJG.js";var d={type:"object",additionalProperties:!1,required:["summary","strengths","actionPlan"],properties:{summary:{type:"string",description:"3-5 sentence executive summary of the audit for the site owner"},strengths:{type:"array",items:{type:"string"},description:"What the site already does well"},actionPlan:{type:"array",description:"A dependency graph, not a list: priority 1 is the highest-leverage constraint that gates other wins.",items:{type:"object",additionalProperties:!1,required:["priority","title","detail","impact","effort","unblocks","failureSignal","leadingIndicator"],properties:{priority:{type:"integer",minimum:1},title:{type:"string"},detail:{type:"string",description:"Specific how-to for THIS site, referencing actual URLs/pages where possible"},impact:{type:"string",enum:["high","medium","low"]},effort:{type:"string",enum:["high","medium","low"]},unblocks:{type:"string",description:"What later actions or outcomes this unblocks; empty string if independent"},failureSignal:{type:"string",description:"How we would know this action FAILED to deliver \u2014 every recommendation must be falsifiable, no invisible bets"},leadingIndicator:{type:"string",description:"The earliest metric that will move if this works (e.g. impressions for X, crawl stats, citation appearance)"}}}}}};async function p(n,t){let r=t.findings.map(e=>({rule:e.ruleId,foundBy:e.source,category:e.category,severity:e.severity,title:e.title,occurrences:e.occurrences,examples:e.evidence.slice(0,3).map(c=>c.location)})),o=`You are a senior SEO and AEO (answer engine optimization) consultant writing up an audit. The findings come from two rounds \u2014 a deterministic rule engine and an AI audit pass \u2014 plus content samples. Produce the report for the site owner. Be concrete and specific to this subject \u2014 never generic advice. Do not invent new findings; synthesize the given ones into themes and a plan.
|
|
3
|
+
|
|
4
|
+
Synthesis method (apply, don't mention):
|
|
5
|
+
1. CONSTRAINT FIRST \u2014 identify the single highest-leverage constraint binding this site right now; often one technical defect gates everything else. It goes first in the action plan.
|
|
6
|
+
2. CONNECT \u2014 look for findings that are symptoms of a shared cause (e.g. thin content on many pages + client-side rendering = one rendering fix, not thirty content rewrites). A finding that survives connection unchanged deserves skepticism \u2014 it might be a symptom, not a cause.
|
|
7
|
+
3. REALITY CHECK \u2014 would the recommendation make the page worse for a human reader, or exceed what a small team can actually operate? Scale advice to the operator, not to an imaginary enterprise.
|
|
8
|
+
4. NO INVISIBLE BETS \u2014 every action carries a failure signal (how we'd know it did NOT work) and a leading indicator (the earliest metric that moves if it does). Order actions so dependencies come before the things they unblock.`,s=[`Audit subject: ${t.subject}`,`Scores (computed, do not change): overall ${t.scorecard.overall}/100; `+t.scorecard.categories.map(e=>`${e.label} ${e.score}`).join(", "),`
|
|
9
|
+
Findings (JSON):
|
|
10
|
+
${JSON.stringify(r,null,1)}`,`
|
|
11
|
+
Content samples:
|
|
12
|
+
${t.contentSamples.slice(0,5).map(e=>`--- ${e.location}
|
|
13
|
+
${e.sample.slice(0,1200)}`).join(`
|
|
14
|
+
`)}`,`
|
|
15
|
+
Write the executive summary, list genuine strengths, and produce a prioritized action plan (max 10 items) ordered by impact-per-effort.`].join(`
|
|
16
|
+
`),a=await n.analyzeJson({system:o,prompt:s,jsonSchema:d});return i.parse(a)}export{p as a};
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import{z as e}from"zod";var o=e.enum(["claude","openai","gemini"]),j=e.object({provider:o,model:e.string().optional(),authMethod:e.enum(["subscription","api-key"]).optional(),anthropicApiKey:e.string().optional(),openaiApiKey:e.string().optional(),geminiApiKey:e.string().optional(),crawl:e.object({maxPages:e.number().int().min(1).max(200).default(25),timeoutMs:e.number().int().default(1e4),render:e.boolean().default(!1)}).default({maxPages:25,timeoutMs:1e4,render:!1}),performance:e.boolean().default(!0),psiApiKey:e.string().optional(),dataForSeoLogin:e.string().optional(),dataForSeoPassword:e.string().optional(),gsc:e.object({clientId:e.string(),clientSecret:e.string(),refreshToken:e.string().optional()}).optional(),createdAt:e.string().optional(),updatedAt:e.string().optional()}),a=e.enum(["critical","warning","info","pass"]),t=e.enum(["technical","content","structured-data","aeo","performance"]),s=e.object({location:e.string(),snippet:e.string().optional()}),r=e.object({id:e.string(),ruleId:e.string(),category:t,severity:a,title:e.string(),description:e.string(),recommendation:e.string(),evidence:e.array(s).default([]),occurrences:e.number().int().default(1),source:e.enum(["rule","ai"]).default("rule"),disputed:e.boolean().optional(),disputeReason:e.string().optional()}),p=e.object({category:t,label:e.string(),score:e.number().min(0).max(100)}),n=e.object({overall:e.number().min(0).max(100),categories:e.array(p)}),g=e.object({priority:e.number().int().min(1),title:e.string(),detail:e.string(),impact:e.enum(["high","medium","low"]),effort:e.enum(["high","medium","low"]),unblocks:e.string().optional(),failureSignal:e.string().optional(),leadingIndicator:e.string().optional()}),i=e.object({summary:e.string(),strengths:e.array(e.string()),actionPlan:e.array(g)}),c=e.object({url:e.string(),status:e.number(),title:e.string().optional(),metaDescription:e.string().optional(),h1:e.string().optional(),wordCount:e.number(),canonical:e.string().optional(),metaRobots:e.string().optional(),jsonLdCount:e.number().int().optional(),hasOg:e.boolean().optional()}),d=e.object({id:e.string(),type:e.literal("domain"),domain:e.string(),createdAt:e.string(),pagesCrawled:e.number(),pages:e.array(c),scorecard:n,findings:e.array(r),llmReport:i.optional()}),m=e.object({keyword:e.string(),intent:e.enum(["informational","navigational","transactional","commercial"]),coverage:e.enum(["strong","partial","missing"]),bestPage:e.string().optional(),aeoCitable:e.boolean(),notes:e.string(),searchVolume:e.number().int().optional(),stage:e.enum(["awareness","consideration","decision","implementation"]).optional(),fanOut:e.array(e.object({query:e.string(),covered:e.boolean()})).optional(),serp:e.object({consensusType:e.string(),sitePageType:e.string(),mismatch:e.enum(["none","medium","high","critical"]),competitorsSeen:e.array(e.string()),advice:e.string()}).optional()}),l=e.object({id:e.string(),type:e.literal("keywords"),auditId:e.string(),domain:e.string(),createdAt:e.string(),keywords:e.array(m),gaps:e.array(e.object({keyword:e.string(),rationale:e.string()})),summary:e.string()}),y=e.object({id:e.string(),type:e.literal("codebase"),path:e.string(),framework:e.string(),createdAt:e.string(),filesScanned:e.number(),scorecard:n,findings:e.array(r),llmReport:i.optional()}),u=e.object({prompt:e.string(),presence:e.enum(["recommended","primary","mentioned","absent"]),framing:e.enum(["favorable","neutral","hedged","negative"]).optional(),recommendedAgainst:e.boolean().optional(),answerExcerpt:e.string(),competitors:e.array(e.string())}),f=e.object({engine:e.string(),score:e.number().min(0).max(100),notes:e.array(e.string())}),b=e.object({id:e.string(),type:e.literal("ai-visibility"),domain:e.string(),createdAt:e.string(),visibilityScore:e.number().min(0).max(100),prompts:e.array(u),competitorLeaderboard:e.array(e.object({name:e.string(),count:e.number().int()})),engineReadiness:e.array(f).optional(),summary:e.string()}),x=e.object({domain:e.string(),scores:e.object({depth:e.number(),formatting:e.number(),seo:e.number(),ux:e.number()}),strength:e.string(),weakness:e.string()}),R=e.object({id:e.string(),type:e.literal("content-brief"),domain:e.string(),keyword:e.string(),createdAt:e.string(),searchIntent:e.string(),recommendedPageType:e.string(),titleSuggestion:e.string(),metaDescription:e.string(),informationGain:e.string(),outline:e.array(e.object({heading:e.string(),keyPoints:e.array(e.string()),targetWords:e.number().int()})),faq:e.array(e.object({question:e.string(),answerAngle:e.string()})),internalLinks:e.array(e.string()),competitors:e.array(x)}),h=e.discriminatedUnion("type",[d,l,y,b,R]);export{j as a,r as b,i as c,d,l as e,y as f,b as g,R as h,h as i};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import{c as i}from"./chunk-SPX2RA5T.js";var p={type:"object",additionalProperties:!1,required:["summary","strengths","actionPlan"],properties:{summary:{type:"string",description:"3-5 sentence executive summary of the audit for the site owner"},strengths:{type:"array",items:{type:"string"},description:"What the site already does well"},actionPlan:{type:"array",description:"Prioritized, concrete actions. Priority 1 = do first.",items:{type:"object",additionalProperties:!1,required:["priority","title","detail","impact","effort"],properties:{priority:{type:"integer",minimum:1},title:{type:"string"},detail:{type:"string",description:"Specific how-to for THIS site, referencing actual URLs/pages where possible"},impact:{type:"string",enum:["high","medium","low"]},effort:{type:"string",enum:["high","medium","low"]}}}}}};async function l(n,t){let r=t.findings.map(e=>({rule:e.ruleId,foundBy:e.source,category:e.category,severity:e.severity,title:e.title,occurrences:e.occurrences,examples:e.evidence.slice(0,3).map(c=>c.location)})),o="You are a senior SEO and AEO (answer engine optimization) consultant writing up an audit. The findings come from two rounds \u2014 a deterministic rule engine and an AI audit pass \u2014 plus content samples. Produce the report for the site owner. Be concrete and specific to this subject \u2014 never generic advice. Do not invent new findings; synthesize the given ones into themes and a plan.",s=[`Audit subject: ${t.subject}`,`Scores (computed, do not change): overall ${t.scorecard.overall}/100; `+t.scorecard.categories.map(e=>`${e.label} ${e.score}`).join(", "),`
|
|
3
|
+
Findings (JSON):
|
|
4
|
+
${JSON.stringify(r,null,1)}`,`
|
|
5
|
+
Content samples:
|
|
6
|
+
${t.contentSamples.slice(0,5).map(e=>`--- ${e.location}
|
|
7
|
+
${e.sample.slice(0,1200)}`).join(`
|
|
8
|
+
`)}`,`
|
|
9
|
+
Write the executive summary, list genuine strengths, and produce a prioritized action plan (max 10 items) ordered by impact-per-effort.`].join(`
|
|
10
|
+
`),a=await n.analyzeJson({system:o,prompt:s,jsonSchema:p});return i.parse(a)}export{l as a};
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import{z as e}from"zod";var i=e.enum(["claude","openai","gemini"]),A=e.object({provider:i,model:e.string().optional(),authMethod:e.enum(["subscription","api-key"]).optional(),anthropicApiKey:e.string().optional(),openaiApiKey:e.string().optional(),geminiApiKey:e.string().optional(),crawl:e.object({maxPages:e.number().int().min(1).max(200).default(25),timeoutMs:e.number().int().default(1e4),render:e.boolean().default(!1)}).default({maxPages:25,timeoutMs:1e4,render:!1}),performance:e.boolean().default(!0),psiApiKey:e.string().optional(),dataForSeoLogin:e.string().optional(),dataForSeoPassword:e.string().optional(),gsc:e.object({clientId:e.string(),clientSecret:e.string(),refreshToken:e.string().optional()}).optional(),createdAt:e.string().optional(),updatedAt:e.string().optional()}),a=e.enum(["critical","warning","info","pass"]),t=e.enum(["technical","content","structured-data","aeo","performance"]),s=e.object({location:e.string(),snippet:e.string().optional()}),r=e.object({id:e.string(),ruleId:e.string(),category:t,severity:a,title:e.string(),description:e.string(),recommendation:e.string(),evidence:e.array(s).default([]),occurrences:e.number().int().default(1),source:e.enum(["rule","ai"]).default("rule")}),p=e.object({category:t,label:e.string(),score:e.number().min(0).max(100)}),n=e.object({overall:e.number().min(0).max(100),categories:e.array(p)}),g=e.object({priority:e.number().int().min(1),title:e.string(),detail:e.string(),impact:e.enum(["high","medium","low"]),effort:e.enum(["high","medium","low"])}),o=e.object({summary:e.string(),strengths:e.array(e.string()),actionPlan:e.array(g)}),c=e.object({url:e.string(),status:e.number(),title:e.string().optional(),metaDescription:e.string().optional(),h1:e.string().optional(),wordCount:e.number(),canonical:e.string().optional(),metaRobots:e.string().optional(),jsonLdCount:e.number().int().optional(),hasOg:e.boolean().optional()}),d=e.object({id:e.string(),type:e.literal("domain"),domain:e.string(),createdAt:e.string(),pagesCrawled:e.number(),pages:e.array(c),scorecard:n,findings:e.array(r),llmReport:o.optional()}),l=e.object({keyword:e.string(),intent:e.enum(["informational","navigational","transactional","commercial"]),coverage:e.enum(["strong","partial","missing"]),bestPage:e.string().optional(),aeoCitable:e.boolean(),notes:e.string(),searchVolume:e.number().int().optional(),stage:e.enum(["awareness","consideration","decision","implementation"]).optional(),fanOut:e.array(e.object({query:e.string(),covered:e.boolean()})).optional()}),m=e.object({id:e.string(),type:e.literal("keywords"),auditId:e.string(),domain:e.string(),createdAt:e.string(),keywords:e.array(l),gaps:e.array(e.object({keyword:e.string(),rationale:e.string()})),summary:e.string()}),y=e.object({id:e.string(),type:e.literal("codebase"),path:e.string(),framework:e.string(),createdAt:e.string(),filesScanned:e.number(),scorecard:n,findings:e.array(r),llmReport:o.optional()}),u=e.object({prompt:e.string(),presence:e.enum(["recommended","primary","mentioned","absent"]),framing:e.enum(["favorable","neutral","hedged","negative"]).optional(),recommendedAgainst:e.boolean().optional(),answerExcerpt:e.string(),competitors:e.array(e.string())}),f=e.object({engine:e.string(),score:e.number().min(0).max(100),notes:e.array(e.string())}),b=e.object({id:e.string(),type:e.literal("ai-visibility"),domain:e.string(),createdAt:e.string(),visibilityScore:e.number().min(0).max(100),prompts:e.array(u),competitorLeaderboard:e.array(e.object({name:e.string(),count:e.number().int()})),engineReadiness:e.array(f).optional(),summary:e.string()}),R=e.discriminatedUnion("type",[d,m,y,b]);export{A as a,r as b,o as c,d,m as e,y as f,b as g,R as h};
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import{z as e}from"zod";var o=e.enum(["claude","openai","gemini"]),j=e.object({provider:o,model:e.string().optional(),authMethod:e.enum(["subscription","api-key"]).optional(),anthropicApiKey:e.string().optional(),openaiApiKey:e.string().optional(),geminiApiKey:e.string().optional(),crawl:e.object({maxPages:e.number().int().min(1).max(200).default(25),timeoutMs:e.number().int().default(1e4),render:e.boolean().default(!1)}).default({maxPages:25,timeoutMs:1e4,render:!1}),performance:e.boolean().default(!0),psiApiKey:e.string().optional(),dataForSeoLogin:e.string().optional(),dataForSeoPassword:e.string().optional(),gsc:e.object({clientId:e.string(),clientSecret:e.string(),refreshToken:e.string().optional()}).optional(),createdAt:e.string().optional(),updatedAt:e.string().optional()}),a=e.enum(["critical","warning","info","pass"]),t=e.enum(["technical","content","structured-data","aeo","performance"]),s=e.object({location:e.string(),snippet:e.string().optional()}),r=e.object({id:e.string(),ruleId:e.string(),category:t,severity:a,title:e.string(),description:e.string(),recommendation:e.string(),evidence:e.array(s).default([]),occurrences:e.number().int().default(1),source:e.enum(["rule","ai"]).default("rule")}),p=e.object({category:t,label:e.string(),score:e.number().min(0).max(100)}),n=e.object({overall:e.number().min(0).max(100),categories:e.array(p)}),g=e.object({priority:e.number().int().min(1),title:e.string(),detail:e.string(),impact:e.enum(["high","medium","low"]),effort:e.enum(["high","medium","low"]),unblocks:e.string().optional(),failureSignal:e.string().optional(),leadingIndicator:e.string().optional()}),i=e.object({summary:e.string(),strengths:e.array(e.string()),actionPlan:e.array(g)}),c=e.object({url:e.string(),status:e.number(),title:e.string().optional(),metaDescription:e.string().optional(),h1:e.string().optional(),wordCount:e.number(),canonical:e.string().optional(),metaRobots:e.string().optional(),jsonLdCount:e.number().int().optional(),hasOg:e.boolean().optional()}),m=e.object({id:e.string(),type:e.literal("domain"),domain:e.string(),createdAt:e.string(),pagesCrawled:e.number(),pages:e.array(c),scorecard:n,findings:e.array(r),llmReport:i.optional()}),d=e.object({keyword:e.string(),intent:e.enum(["informational","navigational","transactional","commercial"]),coverage:e.enum(["strong","partial","missing"]),bestPage:e.string().optional(),aeoCitable:e.boolean(),notes:e.string(),searchVolume:e.number().int().optional(),stage:e.enum(["awareness","consideration","decision","implementation"]).optional(),fanOut:e.array(e.object({query:e.string(),covered:e.boolean()})).optional(),serp:e.object({consensusType:e.string(),sitePageType:e.string(),mismatch:e.enum(["none","medium","high","critical"]),competitorsSeen:e.array(e.string()),advice:e.string()}).optional()}),l=e.object({id:e.string(),type:e.literal("keywords"),auditId:e.string(),domain:e.string(),createdAt:e.string(),keywords:e.array(d),gaps:e.array(e.object({keyword:e.string(),rationale:e.string()})),summary:e.string()}),y=e.object({id:e.string(),type:e.literal("codebase"),path:e.string(),framework:e.string(),createdAt:e.string(),filesScanned:e.number(),scorecard:n,findings:e.array(r),llmReport:i.optional()}),u=e.object({prompt:e.string(),presence:e.enum(["recommended","primary","mentioned","absent"]),framing:e.enum(["favorable","neutral","hedged","negative"]).optional(),recommendedAgainst:e.boolean().optional(),answerExcerpt:e.string(),competitors:e.array(e.string())}),f=e.object({engine:e.string(),score:e.number().min(0).max(100),notes:e.array(e.string())}),b=e.object({id:e.string(),type:e.literal("ai-visibility"),domain:e.string(),createdAt:e.string(),visibilityScore:e.number().min(0).max(100),prompts:e.array(u),competitorLeaderboard:e.array(e.object({name:e.string(),count:e.number().int()})),engineReadiness:e.array(f).optional(),summary:e.string()}),x=e.object({domain:e.string(),scores:e.object({depth:e.number(),formatting:e.number(),seo:e.number(),ux:e.number()}),strength:e.string(),weakness:e.string()}),R=e.object({id:e.string(),type:e.literal("content-brief"),domain:e.string(),keyword:e.string(),createdAt:e.string(),searchIntent:e.string(),recommendedPageType:e.string(),titleSuggestion:e.string(),metaDescription:e.string(),informationGain:e.string(),outline:e.array(e.object({heading:e.string(),keyPoints:e.array(e.string()),targetWords:e.number().int()})),faq:e.array(e.object({question:e.string(),answerAngle:e.string()})),internalLinks:e.array(e.string()),competitors:e.array(x)}),h=e.discriminatedUnion("type",[m,l,y,b,R]);export{j as a,r as b,i as c,m as d,l as e,y as f,b as g,R as h,h as i};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import{c as i}from"./chunk-UW3BLXEX.js";var p={type:"object",additionalProperties:!1,required:["summary","strengths","actionPlan"],properties:{summary:{type:"string",description:"3-5 sentence executive summary of the audit for the site owner"},strengths:{type:"array",items:{type:"string"},description:"What the site already does well"},actionPlan:{type:"array",description:"Prioritized, concrete actions. Priority 1 = do first.",items:{type:"object",additionalProperties:!1,required:["priority","title","detail","impact","effort"],properties:{priority:{type:"integer",minimum:1},title:{type:"string"},detail:{type:"string",description:"Specific how-to for THIS site, referencing actual URLs/pages where possible"},impact:{type:"string",enum:["high","medium","low"]},effort:{type:"string",enum:["high","medium","low"]}}}}}};async function l(n,t){let r=t.findings.map(e=>({rule:e.ruleId,foundBy:e.source,category:e.category,severity:e.severity,title:e.title,occurrences:e.occurrences,examples:e.evidence.slice(0,3).map(c=>c.location)})),o="You are a senior SEO and AEO (answer engine optimization) consultant writing up an audit. The findings come from two rounds \u2014 a deterministic rule engine and an AI audit pass \u2014 plus content samples. Produce the report for the site owner. Be concrete and specific to this subject \u2014 never generic advice. Do not invent new findings; synthesize the given ones into themes and a plan.",s=[`Audit subject: ${t.subject}`,`Scores (computed, do not change): overall ${t.scorecard.overall}/100; `+t.scorecard.categories.map(e=>`${e.label} ${e.score}`).join(", "),`
|
|
3
|
+
Findings (JSON):
|
|
4
|
+
${JSON.stringify(r,null,1)}`,`
|
|
5
|
+
Content samples:
|
|
6
|
+
${t.contentSamples.slice(0,5).map(e=>`--- ${e.location}
|
|
7
|
+
${e.sample.slice(0,1200)}`).join(`
|
|
8
|
+
`)}`,`
|
|
9
|
+
Write the executive summary, list genuine strengths, and produce a prioritized action plan (max 10 items) ordered by impact-per-effort.`].join(`
|
|
10
|
+
`),a=await n.analyzeJson({system:o,prompt:s,jsonSchema:p});return i.parse(a)}export{l as a};
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import{b as u}from"./chunk-3ASHCSJG.js";import b from"fs";import g from"path";import{z as p}from"zod";var h={type:"object",additionalProperties:!1,required:["findings"],properties:{findings:{type:"array",maxItems:12,items:{type:"object",additionalProperties:!1,required:["category","severity","title","description","recommendation","evidence"],properties:{category:{type:"string",enum:["technical","content","structured-data","aeo"]},severity:{type:"string",enum:["critical","warning","info"]},title:{type:"string",description:"Short, specific issue title"},description:{type:"string",description:"What is wrong and why it hurts SEO/AEO, specific to THIS subject"},recommendation:{type:"string",description:"Concrete fix for this specific site/codebase"},evidence:{type:"array",maxItems:5,items:{type:"object",additionalProperties:!1,required:["location"],properties:{location:{type:"string",description:"Exact URL or file path where you observed this"},snippet:{type:"string"}}}}}}}}},x=p.object({findings:p.array(u.pick({category:!0,severity:!0,title:!0,description:!0,recommendation:!0}).extend({evidence:p.array(p.object({location:p.string(),snippet:p.string().optional()}))}))}),y=0;function f(r){return x.parse(r).findings.map(n=>u.parse({...n,id:`ai-${++y}`,ruleId:"ai-audit",occurrences:Math.max(1,n.evidence.length),source:"ai"}))}function w(r){return r.map(t=>`- ${t.ruleId}: ${t.title}`).join(`
|
|
3
|
+
`)}function S(r,t){let n=[],s=0;for(let i of r){let a=i.evidence.filter(c=>{let o=c.location.replace(/^\.\//,""),e=g.isAbsolute(o)?o:g.join(t,o);return e.startsWith(g.resolve(t))&&b.existsSync(e)});a.length>0?n.push({...i,evidence:a,occurrences:Math.max(1,a.length)}):s++}return{kept:n,dropped:s}}function m(r,t){let n=new URL(t).host.replace(/^www\./,""),s=[],i=0;for(let a of r){let c=a.evidence.filter(o=>{try{return new URL(o.location).host.replace(/^www\./,"")===n}catch{return!1}});c.length>0?s.push({...a,evidence:c,occurrences:Math.max(1,c.length)}):i++}return{kept:s,dropped:i}}var v=`Report ONLY issues NOT already covered by the rule findings listed above \u2014 your value is finding what mechanical rules cannot: content quality, messaging clarity, topical authority, E-E-A-T signals, answerability for AI engines, information architecture, intent mismatches, misleading markup, framework-specific pitfalls.
|
|
4
|
+
|
|
5
|
+
# Before you flag something
|
|
6
|
+
- Be certain. If you are going to call something an issue, you must be confident it actually is one for THIS subject. Evidence before synthesis: inspect the actual content yourself before producing a finding \u2014 never assert what a page or file contains without having observed it in this session.
|
|
7
|
+
- Do not invent hypothetical problems. If an edge case matters, the description must state the realistic scenario in which it hurts rankings or AI citations.
|
|
8
|
+
- If you are unsure about something and cannot verify it, omit it. Returning few findings \u2014 or none \u2014 is a valid, good outcome. Prioritize truthfulness over appearing thorough.
|
|
9
|
+
|
|
10
|
+
# Evidence contract
|
|
11
|
+
- Every finding MUST cite evidence you actually observed: the exact URL or exact file path, plus the literal offending snippet where possible. A finding without a real locator is not a finding and will be discarded.
|
|
12
|
+
- Never guess URLs or file paths. Use only locations you saw in the provided data or opened yourself.
|
|
13
|
+
|
|
14
|
+
# Severity calibration \u2014 do not overstate
|
|
15
|
+
- critical: actively losing rankings or AI citations today, on important pages.
|
|
16
|
+
- warning: a real, meaningful lost opportunity.
|
|
17
|
+
- info: polish. When severity depends on circumstances, choose the lower level and state the circumstances in the description.
|
|
18
|
+
|
|
19
|
+
Complete ALL investigation before producing your final structured answer. Base every statement on what you verified, not on what is typical for sites/projects like this.`;function I(r){let t=[],n=s=>new Set(s.toLowerCase().split(/\W+/).filter(i=>i.length>2));for(let s of r){let i=n(s.title);t.some(c=>{let o=n(c.title),e=0;for(let l of i)o.has(l)&&e++;return e/Math.max(1,Math.min(i.size,o.size))>.6})||t.push(s)}return t}var A=[{name:"technical + content",focus:"Focus ONLY on: technical SEO (crawlability, canonicalization, redirects, rendering, indexation) and content quality (E-E-A-T, topical authority, messaging clarity, information architecture). Ignore AEO/AI-engine topics \u2014 another auditor covers those."},{name:"AEO / citability",focus:"Focus ONLY on: answer-engine optimization \u2014 citability, extractable answer structure, entity clarity, schema strategy for AI, freshness signals, AI-crawler access, and whether this content would be selected by AI answers over competitors'. Ignore classic technical SEO \u2014 another auditor covers it."}],k=15;async function L(r,t,n){let s=t.pages.map(e=>({url:e.url,status:e.status,title:e.title,metaDescription:e.metaDescription,canonical:e.canonical,h1s:e.h1s,headings:e.headings.slice(0,20).map(l=>`h${l.level}: ${l.text}`),jsonLdTypes:e.jsonLd.length,og:e.og,wordCount:e.wordCount,textSample:e.textSample.slice(0,1500)})),i="You are a senior SEO/AEO auditor doing the second, human-quality pass of a website audit. A deterministic rule engine already ran (its findings are listed). Now audit the site the way an expert consultant reading the actual pages would.",a=`Evidence-backed AEO levers to audit against (from published citation studies \u2014 use as weights, do not cite the numbers in findings):
|
|
20
|
+
- Citing sources (+40% AI visibility), specific statistics with sources (+37%), expert quotations with name/title (+30%) are the strongest measured levers; keyword stuffing actively hurts (\u221210%).
|
|
21
|
+
- Self-contained answer passages of ~40\u2013170 words get extracted; direct answers belong in the first sentences of a section, and key content in the first 30% of the page.
|
|
22
|
+
- Freshness compounds: recently-updated content is cited ~3x more; 6+ months stale loses eligibility.
|
|
23
|
+
- Brand mentions across the web (Reddit, YouTube, Wikipedia, press) correlate with AI recommendations far more than backlink authority does.
|
|
24
|
+
- Google AI Overviews and AI Mode cite the same URLs only ~14% of the time \u2014 treat them as separate surfaces.
|
|
25
|
+
- AI crawlers do not execute JavaScript; content must be in server HTML.`,c=e=>[`Site: ${t.origin}`,`robots.txt:
|
|
26
|
+
${t.robots.raw.slice(0,2e3)||"(none)"}`,`Crawled pages (JSON):
|
|
27
|
+
${JSON.stringify(s,null,1)}`,a,e,`Rule findings already reported (do NOT repeat these):
|
|
28
|
+
${w(n)}`,v].filter(Boolean).join(`
|
|
29
|
+
|
|
30
|
+
`);if(t.pages.length>=k){let e=await Promise.allSettled(A.map(d=>r.analyzeJson({system:i,prompt:c(d.focus),jsonSchema:h}))),l=e.flatMap(d=>d.status==="fulfilled"?f(d.value):[]);if(l.length===0&&e.every(d=>d.status==="rejected"))throw e[0].reason;return m(I(l),t.origin).kept}let o=f(await r.analyzeJson({system:i,prompt:c(),jsonSchema:h}));return m(o,t.origin).kept}var E={type:"object",additionalProperties:!1,required:["disputes"],properties:{disputes:{type:"array",maxItems:4,items:{type:"object",additionalProperties:!1,required:["ruleId","reason"],properties:{ruleId:{type:"string",description:"The ruleId of the finding being disputed, exactly as listed"},reason:{type:"string",description:"1-2 sentences: why this is a false positive or non-issue for THIS specific site"}}}}}};async function F(r,t,n){let s=n.filter(e=>e.source==="rule");if(s.length===0)return n;let i=s.map(e=>({ruleId:e.ruleId,severity:e.severity,title:e.title,description:e.description.slice(0,200)})),a=t.pages.slice(0,10).map(e=>({url:e.url,title:e.title,sample:e.textSample.slice(0,500)})),c=await r.analyzeJson({system:"You are the designated dissenter reviewing an automated SEO audit. Generic rules produce false positives on specific sites. Your ONLY job is to identify rule findings that are wrong or irrelevant for THIS particular site given what it actually is. Be conservative: dispute a finding only when you are confident it is a false positive or genuinely inapplicable \u2014 'the site owner might not care' is NOT grounds for dispute. Disputing zero findings is a perfectly good answer.",prompt:`Site: ${t.origin}
|
|
31
|
+
|
|
32
|
+
Sample pages:
|
|
33
|
+
${JSON.stringify(a,null,1)}
|
|
34
|
+
|
|
35
|
+
Rule findings:
|
|
36
|
+
${JSON.stringify(i,null,1)}
|
|
37
|
+
|
|
38
|
+
Which findings, if any, are false positives for this specific site?`,jsonSchema:E}),o=new Map(c.disputes.map(e=>[e.ruleId,e.reason]));return n.map(e=>o.has(e.ruleId)?{...e,disputed:!0,disputeReason:o.get(e.ruleId)}:e)}var O={type:"object",additionalProperties:!1,required:["extractable","plans","problems"],properties:{extractable:{type:"boolean",description:"Could a buyer's AI assistant answer 'what are the plans and prices?' completely and correctly from this text alone?"},plans:{type:"array",items:{type:"object",additionalProperties:!1,required:["name","price"],properties:{name:{type:"string"},price:{type:"string"}}}},problems:{type:"array",items:{type:"string"},description:"What's missing or ambiguous (empty if extractable)"}}};async function N(r,t){let n=t.pages.find(i=>{let a=new URL(i.url).pathname.toLowerCase();return/pricing|plans|prices/.test(a)&&i.wordCount>30&&i.status<300});if(!n)return[];let s=await r.analyzeJson({system:"You simulate an AI shopping assistant. Using ONLY the page text provided \u2014 no outside knowledge, no guessing \u2014 determine the product's plans and prices.",prompt:`Page: ${n.url}
|
|
39
|
+
|
|
40
|
+
Page text:
|
|
41
|
+
${n.textSample}
|
|
42
|
+
|
|
43
|
+
What are the plans and their prices? Report whether this is completely answerable from the text.`,jsonSchema:O});return s.extractable?[]:[u.parse({id:`ai-paste-${++y}`,ruleId:"pricing-paste-test",category:"aeo",severity:"warning",title:"Pricing page fails the AI paste test",description:`An AI assistant given this page's text could not fully answer "what are the plans and prices?". Problems: ${s.problems.join("; ")||"plans/prices not determinable"}. Buyers increasingly delegate vendor comparison to AI agents \u2014 an unreadable pricing page silently drops you from their shortlists.`,recommendation:s.plans.length>0?`Only ${s.plans.length} plan(s) were partially extractable (${s.plans.map(i=>`${i.name}: ${i.price}`).join(", ")}). Put every plan's real price in HTML text and add Offer JSON-LD.`:"Put every plan's real price in HTML text (not images or JS-only widgets) and add Offer JSON-LD per plan.",evidence:[{location:n.url}],occurrences:1,source:"ai"})]}async function $(r,t,n,s,i){let a="You are a senior SEO/AEO engineer auditing a codebase, exactly as if the owner asked you to 'review and improve my SEO'. You have read-only access to the repository: explore it yourself with the available tools (read files, search, list). Prioritize technical accuracy and truthfulness; whenever there is uncertainty, investigate to find the truth rather than assuming. Never make assumptions about the contents of files \u2014 read them.",c=[`<env>
|
|
44
|
+
Repository root: ${t}
|
|
45
|
+
Detected framework: ${n} (verify this yourself \u2014 detection may be wrong)
|
|
46
|
+
Today's date: ${new Date().toDateString()}
|
|
47
|
+
</env>`,"Method: first understand how this project renders pages \u2014 where head tags, routing, templates, and static assets live (this could be WordPress/PHP, Django/Flask, Rails, Next.js, plain HTML, or anything else). Then audit: metadata handling (titles/descriptions/canonicals per route), structured data, Open Graph, robots/sitemap/llms.txt generation, rendering strategy (is content in the HTML crawlers receive?), image alt handling, semantic HTML, redirects/status handling, i18n/hreflang, and AEO readiness (answerable content structure, AI-crawler access). Read the actual files behind every finding \u2014 code that looks wrong in isolation may be correct given surrounding logic, and vice versa.",`Rule findings already reported (do NOT repeat these):
|
|
48
|
+
${w(s)}`,v,"Cite evidence as file paths relative to the repository root, pointing at files you actually opened."].join(`
|
|
49
|
+
|
|
50
|
+
`),o=f(await r.exploreJson({cwd:t,system:a,prompt:c,jsonSchema:h,onEvent:i})),{kept:e,dropped:l}=S(o,t);return l>0&&i?.({kind:"text",message:`Discarded ${l} AI finding(s) with unverifiable evidence`}),e}export{S as a,m as b,I as c,L as d,F as e,N as f,$ as g};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import{c as i}from"./chunk-NJ4T52KL.js";var d={type:"object",additionalProperties:!1,required:["summary","strengths","actionPlan"],properties:{summary:{type:"string",description:"3-5 sentence executive summary of the audit for the site owner"},strengths:{type:"array",items:{type:"string"},description:"What the site already does well"},actionPlan:{type:"array",description:"A dependency graph, not a list: priority 1 is the highest-leverage constraint that gates other wins.",items:{type:"object",additionalProperties:!1,required:["priority","title","detail","impact","effort","unblocks","failureSignal","leadingIndicator"],properties:{priority:{type:"integer",minimum:1},title:{type:"string"},detail:{type:"string",description:"Specific how-to for THIS site, referencing actual URLs/pages where possible"},impact:{type:"string",enum:["high","medium","low"]},effort:{type:"string",enum:["high","medium","low"]},unblocks:{type:"string",description:"What later actions or outcomes this unblocks; empty string if independent"},failureSignal:{type:"string",description:"How we would know this action FAILED to deliver \u2014 every recommendation must be falsifiable, no invisible bets"},leadingIndicator:{type:"string",description:"The earliest metric that will move if this works (e.g. impressions for X, crawl stats, citation appearance)"}}}}}};async function p(n,t){let r=t.findings.map(e=>({rule:e.ruleId,foundBy:e.source,category:e.category,severity:e.severity,title:e.title,occurrences:e.occurrences,examples:e.evidence.slice(0,3).map(c=>c.location)})),o=`You are a senior SEO and AEO (answer engine optimization) consultant writing up an audit. The findings come from two rounds \u2014 a deterministic rule engine and an AI audit pass \u2014 plus content samples. Produce the report for the site owner. Be concrete and specific to this subject \u2014 never generic advice. Do not invent new findings; synthesize the given ones into themes and a plan.
|
|
3
|
+
|
|
4
|
+
Synthesis method (apply, don't mention):
|
|
5
|
+
1. CONSTRAINT FIRST \u2014 identify the single highest-leverage constraint binding this site right now; often one technical defect gates everything else. It goes first in the action plan.
|
|
6
|
+
2. CONNECT \u2014 look for findings that are symptoms of a shared cause (e.g. thin content on many pages + client-side rendering = one rendering fix, not thirty content rewrites). A finding that survives connection unchanged deserves skepticism \u2014 it might be a symptom, not a cause.
|
|
7
|
+
3. REALITY CHECK \u2014 would the recommendation make the page worse for a human reader, or exceed what a small team can actually operate? Scale advice to the operator, not to an imaginary enterprise.
|
|
8
|
+
4. NO INVISIBLE BETS \u2014 every action carries a failure signal (how we'd know it did NOT work) and a leading indicator (the earliest metric that moves if it does). Order actions so dependencies come before the things they unblock.`,s=[`Audit subject: ${t.subject}`,`Scores (computed, do not change): overall ${t.scorecard.overall}/100; `+t.scorecard.categories.map(e=>`${e.label} ${e.score}`).join(", "),`
|
|
9
|
+
Findings (JSON):
|
|
10
|
+
${JSON.stringify(r,null,1)}`,`
|
|
11
|
+
Content samples:
|
|
12
|
+
${t.contentSamples.slice(0,5).map(e=>`--- ${e.location}
|
|
13
|
+
${e.sample.slice(0,1200)}`).join(`
|
|
14
|
+
`)}`,`
|
|
15
|
+
Write the executive summary, list genuine strengths, and produce a prioritized action plan (max 10 items) ordered by impact-per-effort.`].join(`
|
|
16
|
+
`),a=await n.analyzeJson({system:o,prompt:s,jsonSchema:d});return i.parse(a)}export{p as a};
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import{b as p}from"./chunk-4NKZE24G.js";import w from"fs";import u from"path";import{z as d}from"zod";var g={type:"object",additionalProperties:!1,required:["findings"],properties:{findings:{type:"array",maxItems:12,items:{type:"object",additionalProperties:!1,required:["category","severity","title","description","recommendation","evidence"],properties:{category:{type:"string",enum:["technical","content","structured-data","aeo"]},severity:{type:"string",enum:["critical","warning","info"]},title:{type:"string",description:"Short, specific issue title"},description:{type:"string",description:"What is wrong and why it hurts SEO/AEO, specific to THIS subject"},recommendation:{type:"string",description:"Concrete fix for this specific site/codebase"},evidence:{type:"array",maxItems:5,items:{type:"object",additionalProperties:!1,required:["location"],properties:{location:{type:"string",description:"Exact URL or file path where you observed this"},snippet:{type:"string"}}}}}}}}},v=d.object({findings:d.array(p.pick({category:!0,severity:!0,title:!0,description:!0,recommendation:!0}).extend({evidence:d.array(d.object({location:d.string(),snippet:d.string().optional()}))}))}),h=0;function m(r){return v.parse(r).findings.map(n=>p.parse({...n,id:`ai-${++h}`,ruleId:"ai-audit",occurrences:Math.max(1,n.evidence.length),source:"ai"}))}function y(r){return r.map(e=>`- ${e.ruleId}: ${e.title}`).join(`
|
|
3
|
+
`)}function b(r,e){let n=[],s=0;for(let t of r){let a=t.evidence.filter(o=>{let c=o.location.replace(/^\.\//,""),i=u.isAbsolute(c)?c:u.join(e,c);return i.startsWith(u.resolve(e))&&w.existsSync(i)});a.length>0?n.push({...t,evidence:a,occurrences:Math.max(1,a.length)}):s++}return{kept:n,dropped:s}}function x(r,e){let n=new URL(e).host.replace(/^www\./,""),s=[],t=0;for(let a of r){let o=a.evidence.filter(c=>{try{return new URL(c.location).host.replace(/^www\./,"")===n}catch{return!1}});o.length>0?s.push({...a,evidence:o,occurrences:Math.max(1,o.length)}):t++}return{kept:s,dropped:t}}var f=`Report ONLY issues NOT already covered by the rule findings listed above \u2014 your value is finding what mechanical rules cannot: content quality, messaging clarity, topical authority, E-E-A-T signals, answerability for AI engines, information architecture, intent mismatches, misleading markup, framework-specific pitfalls.
|
|
4
|
+
|
|
5
|
+
# Before you flag something
|
|
6
|
+
- Be certain. If you are going to call something an issue, you must be confident it actually is one for THIS subject. Evidence before synthesis: inspect the actual content yourself before producing a finding \u2014 never assert what a page or file contains without having observed it in this session.
|
|
7
|
+
- Do not invent hypothetical problems. If an edge case matters, the description must state the realistic scenario in which it hurts rankings or AI citations.
|
|
8
|
+
- If you are unsure about something and cannot verify it, omit it. Returning few findings \u2014 or none \u2014 is a valid, good outcome. Prioritize truthfulness over appearing thorough.
|
|
9
|
+
|
|
10
|
+
# Evidence contract
|
|
11
|
+
- Every finding MUST cite evidence you actually observed: the exact URL or exact file path, plus the literal offending snippet where possible. A finding without a real locator is not a finding and will be discarded.
|
|
12
|
+
- Never guess URLs or file paths. Use only locations you saw in the provided data or opened yourself.
|
|
13
|
+
|
|
14
|
+
# Severity calibration \u2014 do not overstate
|
|
15
|
+
- critical: actively losing rankings or AI citations today, on important pages.
|
|
16
|
+
- warning: a real, meaningful lost opportunity.
|
|
17
|
+
- info: polish. When severity depends on circumstances, choose the lower level and state the circumstances in the description.
|
|
18
|
+
|
|
19
|
+
Complete ALL investigation before producing your final structured answer. Base every statement on what you verified, not on what is typical for sites/projects like this.`;async function j(r,e,n){let s=e.pages.map(i=>({url:i.url,status:i.status,title:i.title,metaDescription:i.metaDescription,canonical:i.canonical,h1s:i.h1s,headings:i.headings.slice(0,20).map(l=>`h${l.level}: ${l.text}`),jsonLdTypes:i.jsonLd.length,og:i.og,wordCount:i.wordCount,textSample:i.textSample.slice(0,1500)})),t="You are a senior SEO/AEO auditor doing the second, human-quality pass of a website audit. A deterministic rule engine already ran (its findings are listed). Now audit the site the way an expert consultant reading the actual pages would.",o=[`Site: ${e.origin}`,`robots.txt:
|
|
20
|
+
${e.robots.raw.slice(0,2e3)||"(none)"}`,`Crawled pages (JSON):
|
|
21
|
+
${JSON.stringify(s,null,1)}`,`Evidence-backed AEO levers to audit against (from published citation studies \u2014 use as weights, do not cite the numbers in findings):
|
|
22
|
+
- Citing sources (+40% AI visibility), specific statistics with sources (+37%), expert quotations with name/title (+30%) are the strongest measured levers; keyword stuffing actively hurts (\u221210%).
|
|
23
|
+
- Self-contained answer passages of ~40\u2013170 words get extracted; direct answers belong in the first sentences of a section, and key content in the first 30% of the page.
|
|
24
|
+
- Freshness compounds: recently-updated content is cited ~3x more; 6+ months stale loses eligibility.
|
|
25
|
+
- Brand mentions across the web (Reddit, YouTube, Wikipedia, press) correlate with AI recommendations far more than backlink authority does.
|
|
26
|
+
- Google AI Overviews and AI Mode cite the same URLs only ~14% of the time \u2014 treat them as separate surfaces.
|
|
27
|
+
- AI crawlers do not execute JavaScript; content must be in server HTML.`,`Rule findings already reported (do NOT repeat these):
|
|
28
|
+
${y(n)}`,f].join(`
|
|
29
|
+
|
|
30
|
+
`),c=m(await r.analyzeJson({system:t,prompt:o,jsonSchema:g}));return x(c,e.origin).kept}var A={type:"object",additionalProperties:!1,required:["extractable","plans","problems"],properties:{extractable:{type:"boolean",description:"Could a buyer's AI assistant answer 'what are the plans and prices?' completely and correctly from this text alone?"},plans:{type:"array",items:{type:"object",additionalProperties:!1,required:["name","price"],properties:{name:{type:"string"},price:{type:"string"}}}},problems:{type:"array",items:{type:"string"},description:"What's missing or ambiguous (empty if extractable)"}}};async function E(r,e){let n=e.pages.find(t=>{let a=new URL(t.url).pathname.toLowerCase();return/pricing|plans|prices/.test(a)&&t.wordCount>30&&t.status<300});if(!n)return[];let s=await r.analyzeJson({system:"You simulate an AI shopping assistant. Using ONLY the page text provided \u2014 no outside knowledge, no guessing \u2014 determine the product's plans and prices.",prompt:`Page: ${n.url}
|
|
31
|
+
|
|
32
|
+
Page text:
|
|
33
|
+
${n.textSample}
|
|
34
|
+
|
|
35
|
+
What are the plans and their prices? Report whether this is completely answerable from the text.`,jsonSchema:A});return s.extractable?[]:[p.parse({id:`ai-paste-${++h}`,ruleId:"pricing-paste-test",category:"aeo",severity:"warning",title:"Pricing page fails the AI paste test",description:`An AI assistant given this page's text could not fully answer "what are the plans and prices?". Problems: ${s.problems.join("; ")||"plans/prices not determinable"}. Buyers increasingly delegate vendor comparison to AI agents \u2014 an unreadable pricing page silently drops you from their shortlists.`,recommendation:s.plans.length>0?`Only ${s.plans.length} plan(s) were partially extractable (${s.plans.map(t=>`${t.name}: ${t.price}`).join(", ")}). Put every plan's real price in HTML text and add Offer JSON-LD.`:"Put every plan's real price in HTML text (not images or JS-only widgets) and add Offer JSON-LD per plan.",evidence:[{location:n.url}],occurrences:1,source:"ai"})]}async function O(r,e,n,s,t){let a="You are a senior SEO/AEO engineer auditing a codebase, exactly as if the owner asked you to 'review and improve my SEO'. You have read-only access to the repository: explore it yourself with the available tools (read files, search, list). Prioritize technical accuracy and truthfulness; whenever there is uncertainty, investigate to find the truth rather than assuming. Never make assumptions about the contents of files \u2014 read them.",o=[`<env>
|
|
36
|
+
Repository root: ${e}
|
|
37
|
+
Detected framework: ${n} (verify this yourself \u2014 detection may be wrong)
|
|
38
|
+
Today's date: ${new Date().toDateString()}
|
|
39
|
+
</env>`,"Method: first understand how this project renders pages \u2014 where head tags, routing, templates, and static assets live (this could be WordPress/PHP, Django/Flask, Rails, Next.js, plain HTML, or anything else). Then audit: metadata handling (titles/descriptions/canonicals per route), structured data, Open Graph, robots/sitemap/llms.txt generation, rendering strategy (is content in the HTML crawlers receive?), image alt handling, semantic HTML, redirects/status handling, i18n/hreflang, and AEO readiness (answerable content structure, AI-crawler access). Read the actual files behind every finding \u2014 code that looks wrong in isolation may be correct given surrounding logic, and vice versa.",`Rule findings already reported (do NOT repeat these):
|
|
40
|
+
${y(s)}`,f,"Cite evidence as file paths relative to the repository root, pointing at files you actually opened."].join(`
|
|
41
|
+
|
|
42
|
+
`),c=m(await r.exploreJson({cwd:e,system:a,prompt:o,jsonSchema:g,onEvent:t})),{kept:i,dropped:l}=b(c,e);return l>0&&t?.({kind:"text",message:`Discarded ${l} AI finding(s) with unverifiable evidence`}),i}export{b as a,x as b,j as c,E as d,O as e};
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import{z as e}from"zod";var i=e.enum(["claude","openai","gemini"]),v=e.object({provider:i,model:e.string().optional(),authMethod:e.enum(["subscription","api-key"]).optional(),economy:e.boolean().default(!0),lightModel:e.string().optional(),anthropicApiKey:e.string().optional(),openaiApiKey:e.string().optional(),geminiApiKey:e.string().optional(),crawl:e.object({maxPages:e.number().int().min(1).max(200).default(25),timeoutMs:e.number().int().default(1e4),render:e.boolean().default(!1)}).default({maxPages:25,timeoutMs:1e4,render:!1}),performance:e.boolean().default(!0),psiApiKey:e.string().optional(),dataForSeoLogin:e.string().optional(),dataForSeoPassword:e.string().optional(),gsc:e.object({clientId:e.string(),clientSecret:e.string(),refreshToken:e.string().optional()}).optional(),createdAt:e.string().optional(),updatedAt:e.string().optional()}),a=e.enum(["critical","warning","info","pass"]),t=e.enum(["technical","content","structured-data","aeo","performance"]),s=e.object({location:e.string(),snippet:e.string().optional()}),r=e.object({id:e.string(),ruleId:e.string(),category:t,severity:a,title:e.string(),description:e.string(),recommendation:e.string(),evidence:e.array(s).default([]),occurrences:e.number().int().default(1),source:e.enum(["rule","ai"]).default("rule"),disputed:e.boolean().optional(),disputeReason:e.string().optional()}),p=e.object({category:t,label:e.string(),score:e.number().min(0).max(100)}),n=e.object({overall:e.number().min(0).max(100),categories:e.array(p)}),g=e.object({priority:e.number().int().min(1),title:e.string(),detail:e.string(),impact:e.enum(["high","medium","low"]),effort:e.enum(["high","medium","low"]),unblocks:e.string().optional(),failureSignal:e.string().optional(),leadingIndicator:e.string().optional()}),o=e.object({summary:e.string(),strengths:e.array(e.string()),actionPlan:e.array(g)}),c=e.object({url:e.string(),status:e.number(),title:e.string().optional(),metaDescription:e.string().optional(),h1:e.string().optional(),wordCount:e.number(),canonical:e.string().optional(),metaRobots:e.string().optional(),jsonLdCount:e.number().int().optional(),hasOg:e.boolean().optional()}),d=e.object({id:e.string(),type:e.literal("domain"),domain:e.string(),createdAt:e.string(),pagesCrawled:e.number(),pages:e.array(c),scorecard:n,findings:e.array(r),llmReport:o.optional()}),m=e.object({keyword:e.string(),intent:e.enum(["informational","navigational","transactional","commercial"]),coverage:e.enum(["strong","partial","missing"]),bestPage:e.string().optional(),aeoCitable:e.boolean(),notes:e.string(),searchVolume:e.number().int().optional(),stage:e.enum(["awareness","consideration","decision","implementation"]).optional(),fanOut:e.array(e.object({query:e.string(),covered:e.boolean()})).optional(),serp:e.object({consensusType:e.string(),sitePageType:e.string(),mismatch:e.enum(["none","medium","high","critical"]),competitorsSeen:e.array(e.string()),advice:e.string()}).optional()}),l=e.object({id:e.string(),type:e.literal("keywords"),auditId:e.string(),domain:e.string(),createdAt:e.string(),keywords:e.array(m),gaps:e.array(e.object({keyword:e.string(),rationale:e.string()})),summary:e.string()}),y=e.object({id:e.string(),type:e.literal("codebase"),path:e.string(),framework:e.string(),createdAt:e.string(),filesScanned:e.number(),scorecard:n,findings:e.array(r),llmReport:o.optional()}),u=e.object({prompt:e.string(),presence:e.enum(["recommended","primary","mentioned","absent"]),framing:e.enum(["favorable","neutral","hedged","negative"]).optional(),recommendedAgainst:e.boolean().optional(),answerExcerpt:e.string(),competitors:e.array(e.string())}),f=e.object({engine:e.string(),score:e.number().min(0).max(100),notes:e.array(e.string())}),b=e.object({id:e.string(),type:e.literal("ai-visibility"),domain:e.string(),createdAt:e.string(),visibilityScore:e.number().min(0).max(100),prompts:e.array(u),competitorLeaderboard:e.array(e.object({name:e.string(),count:e.number().int()})),engineReadiness:e.array(f).optional(),summary:e.string()}),x=e.object({domain:e.string(),scores:e.object({depth:e.number(),formatting:e.number(),seo:e.number(),ux:e.number()}),strength:e.string(),weakness:e.string()}),R=e.object({id:e.string(),type:e.literal("content-brief"),domain:e.string(),keyword:e.string(),createdAt:e.string(),format:e.enum(["standard","vs-competitor","alternatives","best-roundup"]).default("standard"),competitorName:e.string().optional(),searchIntent:e.string(),recommendedPageType:e.string(),titleSuggestion:e.string(),metaDescription:e.string(),informationGain:e.string(),outline:e.array(e.object({heading:e.string(),keyPoints:e.array(e.string()),targetWords:e.number().int()})),faq:e.array(e.object({question:e.string(),answerAngle:e.string()})),internalLinks:e.array(e.string()),competitors:e.array(x)}),h=e.discriminatedUnion("type",[d,l,y,b,R]);export{v as a,r as b,o as c,d,l as e,y as f,b as g,R as h,h as i};
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import{b as p}from"./chunk-V6QVPBUT.js";import w from"fs";import u from"path";import{z as d}from"zod";var g={type:"object",additionalProperties:!1,required:["findings"],properties:{findings:{type:"array",maxItems:12,items:{type:"object",additionalProperties:!1,required:["category","severity","title","description","recommendation","evidence"],properties:{category:{type:"string",enum:["technical","content","structured-data","aeo"]},severity:{type:"string",enum:["critical","warning","info"]},title:{type:"string",description:"Short, specific issue title"},description:{type:"string",description:"What is wrong and why it hurts SEO/AEO, specific to THIS subject"},recommendation:{type:"string",description:"Concrete fix for this specific site/codebase"},evidence:{type:"array",maxItems:5,items:{type:"object",additionalProperties:!1,required:["location"],properties:{location:{type:"string",description:"Exact URL or file path where you observed this"},snippet:{type:"string"}}}}}}}}},v=d.object({findings:d.array(p.pick({category:!0,severity:!0,title:!0,description:!0,recommendation:!0}).extend({evidence:d.array(d.object({location:d.string(),snippet:d.string().optional()}))}))}),h=0;function m(r){return v.parse(r).findings.map(n=>p.parse({...n,id:`ai-${++h}`,ruleId:"ai-audit",occurrences:Math.max(1,n.evidence.length),source:"ai"}))}function y(r){return r.map(e=>`- ${e.ruleId}: ${e.title}`).join(`
|
|
3
|
+
`)}function b(r,e){let n=[],s=0;for(let t of r){let a=t.evidence.filter(o=>{let c=o.location.replace(/^\.\//,""),i=u.isAbsolute(c)?c:u.join(e,c);return i.startsWith(u.resolve(e))&&w.existsSync(i)});a.length>0?n.push({...t,evidence:a,occurrences:Math.max(1,a.length)}):s++}return{kept:n,dropped:s}}function x(r,e){let n=new URL(e).host.replace(/^www\./,""),s=[],t=0;for(let a of r){let o=a.evidence.filter(c=>{try{return new URL(c.location).host.replace(/^www\./,"")===n}catch{return!1}});o.length>0?s.push({...a,evidence:o,occurrences:Math.max(1,o.length)}):t++}return{kept:s,dropped:t}}var f=`Report ONLY issues NOT already covered by the rule findings listed above \u2014 your value is finding what mechanical rules cannot: content quality, messaging clarity, topical authority, E-E-A-T signals, answerability for AI engines, information architecture, intent mismatches, misleading markup, framework-specific pitfalls.
|
|
4
|
+
|
|
5
|
+
# Before you flag something
|
|
6
|
+
- Be certain. If you are going to call something an issue, you must be confident it actually is one for THIS subject. Evidence before synthesis: inspect the actual content yourself before producing a finding \u2014 never assert what a page or file contains without having observed it in this session.
|
|
7
|
+
- Do not invent hypothetical problems. If an edge case matters, the description must state the realistic scenario in which it hurts rankings or AI citations.
|
|
8
|
+
- If you are unsure about something and cannot verify it, omit it. Returning few findings \u2014 or none \u2014 is a valid, good outcome. Prioritize truthfulness over appearing thorough.
|
|
9
|
+
|
|
10
|
+
# Evidence contract
|
|
11
|
+
- Every finding MUST cite evidence you actually observed: the exact URL or exact file path, plus the literal offending snippet where possible. A finding without a real locator is not a finding and will be discarded.
|
|
12
|
+
- Never guess URLs or file paths. Use only locations you saw in the provided data or opened yourself.
|
|
13
|
+
|
|
14
|
+
# Severity calibration \u2014 do not overstate
|
|
15
|
+
- critical: actively losing rankings or AI citations today, on important pages.
|
|
16
|
+
- warning: a real, meaningful lost opportunity.
|
|
17
|
+
- info: polish. When severity depends on circumstances, choose the lower level and state the circumstances in the description.
|
|
18
|
+
|
|
19
|
+
Complete ALL investigation before producing your final structured answer. Base every statement on what you verified, not on what is typical for sites/projects like this.`;async function j(r,e,n){let s=e.pages.map(i=>({url:i.url,status:i.status,title:i.title,metaDescription:i.metaDescription,canonical:i.canonical,h1s:i.h1s,headings:i.headings.slice(0,20).map(l=>`h${l.level}: ${l.text}`),jsonLdTypes:i.jsonLd.length,og:i.og,wordCount:i.wordCount,textSample:i.textSample.slice(0,1500)})),t="You are a senior SEO/AEO auditor doing the second, human-quality pass of a website audit. A deterministic rule engine already ran (its findings are listed). Now audit the site the way an expert consultant reading the actual pages would.",o=[`Site: ${e.origin}`,`robots.txt:
|
|
20
|
+
${e.robots.raw.slice(0,2e3)||"(none)"}`,`Crawled pages (JSON):
|
|
21
|
+
${JSON.stringify(s,null,1)}`,`Evidence-backed AEO levers to audit against (from published citation studies \u2014 use as weights, do not cite the numbers in findings):
|
|
22
|
+
- Citing sources (+40% AI visibility), specific statistics with sources (+37%), expert quotations with name/title (+30%) are the strongest measured levers; keyword stuffing actively hurts (\u221210%).
|
|
23
|
+
- Self-contained answer passages of ~40\u2013170 words get extracted; direct answers belong in the first sentences of a section, and key content in the first 30% of the page.
|
|
24
|
+
- Freshness compounds: recently-updated content is cited ~3x more; 6+ months stale loses eligibility.
|
|
25
|
+
- Brand mentions across the web (Reddit, YouTube, Wikipedia, press) correlate with AI recommendations far more than backlink authority does.
|
|
26
|
+
- Google AI Overviews and AI Mode cite the same URLs only ~14% of the time \u2014 treat them as separate surfaces.
|
|
27
|
+
- AI crawlers do not execute JavaScript; content must be in server HTML.`,`Rule findings already reported (do NOT repeat these):
|
|
28
|
+
${y(n)}`,f].join(`
|
|
29
|
+
|
|
30
|
+
`),c=m(await r.analyzeJson({system:t,prompt:o,jsonSchema:g}));return x(c,e.origin).kept}var A={type:"object",additionalProperties:!1,required:["extractable","plans","problems"],properties:{extractable:{type:"boolean",description:"Could a buyer's AI assistant answer 'what are the plans and prices?' completely and correctly from this text alone?"},plans:{type:"array",items:{type:"object",additionalProperties:!1,required:["name","price"],properties:{name:{type:"string"},price:{type:"string"}}}},problems:{type:"array",items:{type:"string"},description:"What's missing or ambiguous (empty if extractable)"}}};async function E(r,e){let n=e.pages.find(t=>{let a=new URL(t.url).pathname.toLowerCase();return/pricing|plans|prices/.test(a)&&t.wordCount>30&&t.status<300});if(!n)return[];let s=await r.analyzeJson({system:"You simulate an AI shopping assistant. Using ONLY the page text provided \u2014 no outside knowledge, no guessing \u2014 determine the product's plans and prices.",prompt:`Page: ${n.url}
|
|
31
|
+
|
|
32
|
+
Page text:
|
|
33
|
+
${n.textSample}
|
|
34
|
+
|
|
35
|
+
What are the plans and their prices? Report whether this is completely answerable from the text.`,jsonSchema:A});return s.extractable?[]:[p.parse({id:`ai-paste-${++h}`,ruleId:"pricing-paste-test",category:"aeo",severity:"warning",title:"Pricing page fails the AI paste test",description:`An AI assistant given this page's text could not fully answer "what are the plans and prices?". Problems: ${s.problems.join("; ")||"plans/prices not determinable"}. Buyers increasingly delegate vendor comparison to AI agents \u2014 an unreadable pricing page silently drops you from their shortlists.`,recommendation:s.plans.length>0?`Only ${s.plans.length} plan(s) were partially extractable (${s.plans.map(t=>`${t.name}: ${t.price}`).join(", ")}). Put every plan's real price in HTML text and add Offer JSON-LD.`:"Put every plan's real price in HTML text (not images or JS-only widgets) and add Offer JSON-LD per plan.",evidence:[{location:n.url}],occurrences:1,source:"ai"})]}async function O(r,e,n,s,t){let a="You are a senior SEO/AEO engineer auditing a codebase, exactly as if the owner asked you to 'review and improve my SEO'. You have read-only access to the repository: explore it yourself with the available tools (read files, search, list). Prioritize technical accuracy and truthfulness; whenever there is uncertainty, investigate to find the truth rather than assuming. Never make assumptions about the contents of files \u2014 read them.",o=[`<env>
|
|
36
|
+
Repository root: ${e}
|
|
37
|
+
Detected framework: ${n} (verify this yourself \u2014 detection may be wrong)
|
|
38
|
+
Today's date: ${new Date().toDateString()}
|
|
39
|
+
</env>`,"Method: first understand how this project renders pages \u2014 where head tags, routing, templates, and static assets live (this could be WordPress/PHP, Django/Flask, Rails, Next.js, plain HTML, or anything else). Then audit: metadata handling (titles/descriptions/canonicals per route), structured data, Open Graph, robots/sitemap/llms.txt generation, rendering strategy (is content in the HTML crawlers receive?), image alt handling, semantic HTML, redirects/status handling, i18n/hreflang, and AEO readiness (answerable content structure, AI-crawler access). Read the actual files behind every finding \u2014 code that looks wrong in isolation may be correct given surrounding logic, and vice versa.",`Rule findings already reported (do NOT repeat these):
|
|
40
|
+
${y(s)}`,f,"Cite evidence as file paths relative to the repository root, pointing at files you actually opened."].join(`
|
|
41
|
+
|
|
42
|
+
`),c=m(await r.exploreJson({cwd:e,system:a,prompt:o,jsonSchema:g,onEvent:t})),{kept:i,dropped:l}=b(c,e);return l>0&&t?.({kind:"text",message:`Discarded ${l} AI finding(s) with unverifiable evidence`}),i}export{b as a,x as b,j as c,E as d,O as e};
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
function h(e,t){let n=c=>new Set(c.toLowerCase().split(/\W+/).filter(Boolean)),i=n(e),o=n(t);if(i.size===0&&o.size===0)return 1;let a=0;for(let c of i)o.has(c)&&a++;return a/(i.size+o.size-a)}var d=e=>e.replace(/\/$/,"");function w(e,t){let n={critical:0,warning:1,info:2},i=n[t];return e.some(o=>n[o.severity]<=i)?1:0}function p(e,t){let n=[],i=new Map(e.pages.map(r=>[d(r.url),r])),o=new Map(t.pages.map(r=>[d(r.url),r])),a=(r,s,l,g,f,m)=>n.push({url:r,field:s,severity:l,before:String(g??"(none)"),after:String(f??"(none)"),explanation:m});for(let[r,s]of i){let l=o.get(r);if(!l){a(s.url,"presence","info","crawled","not crawled","Page no longer reached by the crawl (may be crawl variance or a removed link path).");continue}u(s,l,a)}for(let[r,s]of o)i.has(r)||a(s.url,"presence","info","not crawled","crawled","New page appeared in the crawl.");let c={critical:0,warning:1,info:2};return n.sort((r,s)=>c[r.severity]-c[s.severity])}function u(e,t,n){let i=t.url;e.status<400&&t.status>=400&&n(i,"status","critical",e.status,t.status,"Page went from working to an error status \u2014 it will drop out of the index."),e.title&&!t.title?n(i,"title","critical",e.title,t.title,"Title tag removed \u2014 the strongest on-page signal is gone."):e.title&&t.title&&e.title!==t.title&&n(i,"title","warning",e.title,t.title,"Title changed \u2014 verify this was intentional; rankings for the old phrasing may shift."),e.h1&&!t.h1?n(i,"h1","critical",e.h1,t.h1,"H1 removed."):e.h1&&t.h1&&h(e.h1,t.h1)<.5&&n(i,"h1","warning",e.h1,t.h1,"H1 changed substantially \u2014 the page's declared topic moved."),e.canonical&&!t.canonical?n(i,"canonical","critical",e.canonical,t.canonical,"Canonical removed \u2014 duplicate-URL signals are now unmanaged."):e.canonical&&t.canonical&&d(e.canonical)!==d(t.canonical)&&n(i,"canonical","critical",e.canonical,t.canonical,"Canonical now points elsewhere \u2014 this page cedes its ranking to the new target.");let o=/noindex/i.test(e.metaRobots??""),a=/noindex/i.test(t.metaRobots??"");!o&&a&&n(i,"robots","critical",e.metaRobots??"(indexable)",t.metaRobots,"noindex added \u2014 this page will be removed from search results."),(e.jsonLdCount??0)>0&&(t.jsonLdCount??0)===0&&n(i,"structured-data","critical",`${e.jsonLdCount} JSON-LD block(s)`,"0","All structured data removed."),e.metaDescription&&!t.metaDescription&&n(i,"meta-description","warning",e.metaDescription.slice(0,80),"(none)","Meta description removed."),e.hasOg&&t.hasOg===!1&&n(i,"open-graph","warning","present","removed","Open Graph tags removed \u2014 link shares lose their preview cards."),e.wordCount>200&&t.wordCount<e.wordCount*.6&&n(i,"content","warning",`${e.wordCount} words`,`${t.wordCount} words`,"Content shrank by more than 40% \u2014 check for accidentally removed sections or rendering changes.")}export{h as a,w as b,p as c};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import{b as d}from"./chunk-SPX2RA5T.js";import{z as r}from"zod";var l={type:"object",additionalProperties:!1,required:["findings"],properties:{findings:{type:"array",maxItems:12,items:{type:"object",additionalProperties:!1,required:["category","severity","title","description","recommendation","evidence"],properties:{category:{type:"string",enum:["technical","content","structured-data","aeo"]},severity:{type:"string",enum:["critical","warning","info"]},title:{type:"string",description:"Short, specific issue title"},description:{type:"string",description:"What is wrong and why it hurts SEO/AEO, specific to THIS subject"},recommendation:{type:"string",description:"Concrete fix for this specific site/codebase"},evidence:{type:"array",maxItems:5,items:{type:"object",additionalProperties:!1,required:["location"],properties:{location:{type:"string",description:"Exact URL or file path where you observed this"},snippet:{type:"string"}}}}}}}}},y=r.object({findings:r.array(d.pick({category:!0,severity:!0,title:!0,description:!0,recommendation:!0}).extend({evidence:r.array(r.object({location:r.string(),snippet:r.string().optional()}))}))}),m=0;function u(i){return y.parse(i).findings.map(n=>d.parse({...n,id:`ai-${++m}`,ruleId:"ai-audit",occurrences:Math.max(1,n.evidence.length),source:"ai"}))}function p(i){return i.map(t=>`- ${t.ruleId}: ${t.title}`).join(`
|
|
3
|
+
`)}var g="Report ONLY issues NOT already covered by the rule findings listed above \u2014 your value is finding what mechanical rules cannot: content quality, messaging clarity, topical authority, E-E-A-T signals, answerability for AI engines, information architecture, intent mismatches, misleading markup, framework-specific pitfalls. Every finding must cite real evidence you actually observed (exact URLs or file paths). If something is genuinely fine, do not invent problems \u2014 returning few findings is a valid outcome. Severity guide: critical = actively losing rankings/citations today; warning = meaningful lost opportunity; info = polish.";async function v(i,t,n){let s=t.pages.map(e=>({url:e.url,status:e.status,title:e.title,metaDescription:e.metaDescription,canonical:e.canonical,h1s:e.h1s,headings:e.headings.slice(0,20).map(c=>`h${c.level}: ${c.text}`),jsonLdTypes:e.jsonLd.length,og:e.og,wordCount:e.wordCount,textSample:e.textSample.slice(0,1500)})),a="You are a senior SEO/AEO auditor doing the second, human-quality pass of a website audit. A deterministic rule engine already ran (its findings are listed). Now audit the site the way an expert consultant reading the actual pages would.",o=[`Site: ${t.origin}`,`robots.txt:
|
|
4
|
+
${t.robots.raw.slice(0,2e3)||"(none)"}`,`Crawled pages (JSON):
|
|
5
|
+
${JSON.stringify(s,null,1)}`,`Rule findings already reported (do NOT repeat these):
|
|
6
|
+
${p(n)}`,g].join(`
|
|
7
|
+
|
|
8
|
+
`);return u(await i.analyzeJson({system:a,prompt:o,jsonSchema:l}))}async function w(i,t,n,s,a){let o="You are a senior SEO/AEO engineer auditing a codebase, exactly as if the owner asked you to 'review and improve my SEO'. You have read-only access to the repository: explore it yourself with the available tools (read files, search, list). Work stack-agnostically \u2014 this could be WordPress/PHP, Django/Flask, Rails, Next.js, plain HTML, or anything else; first understand how this project renders its pages and where head tags, routing, templates, and static assets live, then audit it.",e=[`Repository: ${t} (detected as: ${n} \u2014 verify this yourself, detection may be wrong).`,"Audit for: metadata handling (titles/descriptions/canonicals per route), structured data, Open Graph, robots/sitemap/llms.txt generation, rendering strategy (is content in the HTML crawlers receive?), image alt handling, semantic HTML, redirects/status handling, i18n/hreflang, and AEO readiness (answerable content structure, AI-crawler access).",`Rule findings already reported (do NOT repeat these):
|
|
9
|
+
${p(s)}`,g,"Cite file paths relative to the repository root as evidence."].join(`
|
|
10
|
+
|
|
11
|
+
`);return u(await i.exploreJson({cwd:t,system:o,prompt:e,jsonSchema:l,onEvent:a}))}export{v as a,w as b};
|