@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.
Files changed (73) hide show
  1. package/README.md +75 -10
  2. package/THIRD-PARTY-NOTICES.md +40 -0
  3. package/dist/aiAudit-4VRLOX3Q.js +2 -0
  4. package/dist/aiAudit-5EL5LJKD.js +2 -0
  5. package/dist/aiAudit-CPECFC7E.js +2 -0
  6. package/dist/aiAudit-DRZEPNLG.js +2 -0
  7. package/dist/aiAudit-GK2PWFLY.js +2 -0
  8. package/dist/aiAudit-HNBKV2CD.js +2 -0
  9. package/dist/aiAudit-LARDDH2P.js +2 -0
  10. package/dist/aiAudit-LJDIXAE5.js +2 -0
  11. package/dist/aiAudit-PJJH2G52.js +2 -0
  12. package/dist/aiAudit-Q7XF6GGP.js +2 -0
  13. package/dist/aiAudit-U4INAUJS.js +2 -0
  14. package/dist/aiAudit-WHUM7EJL.js +2 -0
  15. package/dist/aiAudit-YXGRX7CV.js +2 -0
  16. package/dist/chunk-2FHZ2BFC.js +16 -0
  17. package/dist/chunk-3ASHCSJG.js +2 -0
  18. package/dist/chunk-4AINMOLH.js +10 -0
  19. package/dist/chunk-4LCUBQUE.js +2 -0
  20. package/dist/chunk-4NKZE24G.js +2 -0
  21. package/dist/chunk-5HL5QNMH.js +10 -0
  22. package/dist/chunk-6K6Q77LY.js +50 -0
  23. package/dist/chunk-7TI3OPOC.js +16 -0
  24. package/dist/chunk-AC6ELEHI.js +42 -0
  25. package/dist/chunk-AIOAF3VX.js +2 -0
  26. package/dist/chunk-ARTOQXQP.js +42 -0
  27. package/dist/chunk-BSOQNREQ.js +2 -0
  28. package/dist/chunk-BT2ILFUJ.js +11 -0
  29. package/dist/chunk-CDCS5KKM.js +37 -0
  30. package/dist/chunk-CU3RA4ZL.js +37 -0
  31. package/dist/chunk-CWDXO6A5.js +37 -0
  32. package/dist/chunk-DUM3VDXO.js +16 -0
  33. package/dist/chunk-EYZZ47FM.js +31 -0
  34. package/dist/chunk-G3WWINTJ.js +10 -0
  35. package/dist/chunk-GKGSIXNZ.js +42 -0
  36. package/dist/chunk-JOJ27B65.js +19 -0
  37. package/dist/chunk-KM6WE6OO.js +37 -0
  38. package/dist/chunk-KUW3W6JN.js +2 -0
  39. package/dist/chunk-NJ4T52KL.js +2 -0
  40. package/dist/chunk-O3BX4SMT.js +16 -0
  41. package/dist/chunk-OSBRK5KC.js +50 -0
  42. package/dist/chunk-SPX2RA5T.js +2 -0
  43. package/dist/chunk-TZUYZGAN.js +16 -0
  44. package/dist/chunk-U3KERC3T.js +2 -0
  45. package/dist/chunk-UW3BLXEX.js +2 -0
  46. package/dist/chunk-UY6CGWIN.js +2 -0
  47. package/dist/chunk-V6QVPBUT.js +2 -0
  48. package/dist/chunk-WO7RE3KE.js +50 -0
  49. package/dist/chunk-WQF3EL6I.js +37 -0
  50. package/dist/chunk-X7TFHEYT.js +10 -0
  51. package/dist/chunk-XK62BKWH.js +3 -0
  52. package/dist/chunk-YPIZMUWS.js +16 -0
  53. package/dist/cli.js +142 -26
  54. package/dist/crawler-S3FAQ6PB.js +2 -0
  55. package/dist/drift-WE75HT5C.js +2 -0
  56. package/dist/report-5GRFH2IG.js +2 -0
  57. package/dist/report-BT7UL6W3.js +2 -0
  58. package/dist/report-DTYZQFUQ.js +2 -0
  59. package/dist/report-FQHYD5MV.js +2 -0
  60. package/dist/report-KQZTM623.js +2 -0
  61. package/dist/report-OTFGRNMJ.js +2 -0
  62. package/dist/report-UNRVFXFM.js +2 -0
  63. package/dist/report-VEF6N4W2.js +2 -0
  64. package/dist/report-VNSGKYVL.js +2 -0
  65. package/dist/report-YZLGDZH6.js +2 -0
  66. package/dist/store-I6ZAS3QR.js +2 -0
  67. package/dist/store-W5O642L3.js +2 -0
  68. package/dist/ui/assets/index-CFVxJXmH.css +1 -0
  69. package/dist/ui/assets/index-Cmt-9HyO.js +67 -0
  70. package/dist/ui/index.html +2 -2
  71. package/package.json +9 -4
  72. package/dist/ui/assets/index-C8yCj8Iz.css +0 -1
  73. 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, or OpenAI via ChatGPT sign-in / API key),
13
- pick a model, verify the connection — then the web UI opens at
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 ~40 measurable facts (titles,
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 of target keywords against the
35
- crawled content; enriched with **real Search Console queries** (when
36
- connected) and **real search volumes** (DataForSEO, optional).
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
- Config lives in `~/.eternify/config.json` (chmod 600); reports in
71
- `~/.eternify/reports/`. Everything runs locally — page content is sent only to
72
- the AI provider you chose.
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,2 @@
1
+ #!/usr/bin/env node
2
+ import{a,b,c,d}from"./chunk-CU3RA4ZL.js";import"./chunk-4LCUBQUE.js";export{d as aiCodebaseAudit,c as aiDomainAudit,a as groundCodebaseFindings,b as groundDomainFindings};
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ import{a,b,c,d,e,f,g}from"./chunk-OSBRK5KC.js";import"./chunk-AIOAF3VX.js";export{g as aiCodebaseAudit,d as aiDomainAudit,c as dedupeFindings,e as dissentRuleFindings,a as groundCodebaseFindings,b as groundDomainFindings,f as pasteTestPricing};
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ import{a,b,c,d,e,f}from"./chunk-GKGSIXNZ.js";import"./chunk-4NKZE24G.js";export{f as aiCodebaseAudit,d as aiDomainAudit,c as dedupeFindings,a as groundCodebaseFindings,b as groundDomainFindings,e as pasteTestPricing};
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ import{a,b,c,d}from"./chunk-CWDXO6A5.js";import"./chunk-UY6CGWIN.js";export{d as aiCodebaseAudit,c as aiDomainAudit,a as groundCodebaseFindings,b as groundDomainFindings};
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ import{a,b,c,d,e,f,g}from"./chunk-WO7RE3KE.js";import"./chunk-U3KERC3T.js";export{g as aiCodebaseAudit,d as aiDomainAudit,c as dedupeFindings,e as dissentRuleFindings,a as groundCodebaseFindings,b as groundDomainFindings,f as pasteTestPricing};
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ import{a,b,c,d,e}from"./chunk-ARTOQXQP.js";import"./chunk-V6QVPBUT.js";export{e as aiCodebaseAudit,c as aiDomainAudit,a as groundCodebaseFindings,b as groundDomainFindings,d as pasteTestPricing};
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ import{a,b,c,d}from"./chunk-EYZZ47FM.js";import"./chunk-SPX2RA5T.js";export{d as aiCodebaseAudit,c as aiDomainAudit,a as groundCodebaseFindings,b as groundDomainFindings};
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ import{a,b,c,d}from"./chunk-WQF3EL6I.js";import"./chunk-NJ4T52KL.js";export{d as aiCodebaseAudit,c as aiDomainAudit,a as groundCodebaseFindings,b as groundDomainFindings};
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ import{a,b,c,d}from"./chunk-KM6WE6OO.js";import"./chunk-UW3BLXEX.js";export{d as aiCodebaseAudit,c as aiDomainAudit,a as groundCodebaseFindings,b as groundDomainFindings};
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ import{a,b,c,d,e,f,g}from"./chunk-6K6Q77LY.js";import"./chunk-3ASHCSJG.js";export{g as aiCodebaseAudit,d as aiDomainAudit,c as dedupeFindings,e as dissentRuleFindings,a as groundCodebaseFindings,b as groundDomainFindings,f as pasteTestPricing};
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ import{a,b}from"./chunk-BT2ILFUJ.js";import"./chunk-SPX2RA5T.js";export{b as aiCodebaseAudit,a as aiDomainAudit};
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ import{a,b,c,d,e}from"./chunk-AC6ELEHI.js";import"./chunk-4NKZE24G.js";export{e as aiCodebaseAudit,c as aiDomainAudit,a as groundCodebaseFindings,b as groundDomainFindings,d as pasteTestPricing};
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ import{a,b,c,d}from"./chunk-CDCS5KKM.js";import"./chunk-SPX2RA5T.js";export{d as aiCodebaseAudit,c as aiDomainAudit,a as groundCodebaseFindings,b as groundDomainFindings};
@@ -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};