@eternify-labs/seo-audit 0.1.0 → 0.3.0
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 +138 -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 +10 -5
- package/dist/ui/assets/index-C8yCj8Iz.css +0 -1
- package/dist/ui/assets/index-DB7HIYjL.js +0 -67
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import{b as u}from"./chunk-SPX2RA5T.js";import y from"fs";import p 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(u.pick({category:!0,severity:!0,title:!0,description:!0,recommendation:!0}).extend({evidence:d.array(d.object({location:d.string(),snippet:d.string().optional()}))}))}),w=0;function h(i){return v.parse(i).findings.map(n=>u.parse({...n,id:`ai-${++w}`,ruleId:"ai-audit",occurrences:Math.max(1,n.evidence.length),source:"ai"}))}function f(i){return i.map(e=>`- ${e.ruleId}: ${e.title}`).join(`
|
|
3
|
+
`)}function b(i,e){let n=[],o=0;for(let s of i){let r=s.evidence.filter(a=>{let c=a.location.replace(/^\.\//,""),t=p.isAbsolute(c)?c:p.join(e,c);return t.startsWith(p.resolve(e))&&y.existsSync(t)});r.length>0?n.push({...s,evidence:r,occurrences:Math.max(1,r.length)}):o++}return{kept:n,dropped:o}}function x(i,e){let n=new URL(e).host.replace(/^www\./,""),o=[],s=0;for(let r of i){let a=r.evidence.filter(c=>{try{return new URL(c.location).host.replace(/^www\./,"")===n}catch{return!1}});a.length>0?o.push({...r,evidence:a,occurrences:Math.max(1,a.length)}):s++}return{kept:o,dropped:s}}var m=`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 I(i,e,n){let o=e.pages.map(t=>({url:t.url,status:t.status,title:t.title,metaDescription:t.metaDescription,canonical:t.canonical,h1s:t.h1s,headings:t.headings.slice(0,20).map(l=>`h${l.level}: ${l.text}`),jsonLdTypes:t.jsonLd.length,og:t.og,wordCount:t.wordCount,textSample:t.textSample.slice(0,1500)})),s="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=[`Site: ${e.origin}`,`robots.txt:
|
|
20
|
+
${e.robots.raw.slice(0,2e3)||"(none)"}`,`Crawled pages (JSON):
|
|
21
|
+
${JSON.stringify(o,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
|
+
${f(n)}`,m].join(`
|
|
29
|
+
|
|
30
|
+
`),c=h(await i.analyzeJson({system:s,prompt:a,jsonSchema:g}));return x(c,e.origin).kept}async function j(i,e,n,o,s){let r="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.",a=[`<env>
|
|
31
|
+
Repository root: ${e}
|
|
32
|
+
Detected framework: ${n} (verify this yourself \u2014 detection may be wrong)
|
|
33
|
+
Today's date: ${new Date().toDateString()}
|
|
34
|
+
</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):
|
|
35
|
+
${f(o)}`,m,"Cite evidence as file paths relative to the repository root, pointing at files you actually opened."].join(`
|
|
36
|
+
|
|
37
|
+
`),c=h(await i.exploreJson({cwd:e,system:r,prompt:a,jsonSchema:g,onEvent:s})),{kept:t,dropped:l}=b(c,e);return l>0&&s?.({kind:"text",message:`Discarded ${l} AI finding(s) with unverifiable evidence`}),t}export{b as a,x as b,I as c,j as d};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import{b as u}from"./chunk-4LCUBQUE.js";import y from"fs";import p 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(u.pick({category:!0,severity:!0,title:!0,description:!0,recommendation:!0}).extend({evidence:d.array(d.object({location:d.string(),snippet:d.string().optional()}))}))}),w=0;function h(i){return v.parse(i).findings.map(n=>u.parse({...n,id:`ai-${++w}`,ruleId:"ai-audit",occurrences:Math.max(1,n.evidence.length),source:"ai"}))}function f(i){return i.map(e=>`- ${e.ruleId}: ${e.title}`).join(`
|
|
3
|
+
`)}function b(i,e){let n=[],o=0;for(let s of i){let r=s.evidence.filter(a=>{let c=a.location.replace(/^\.\//,""),t=p.isAbsolute(c)?c:p.join(e,c);return t.startsWith(p.resolve(e))&&y.existsSync(t)});r.length>0?n.push({...s,evidence:r,occurrences:Math.max(1,r.length)}):o++}return{kept:n,dropped:o}}function x(i,e){let n=new URL(e).host.replace(/^www\./,""),o=[],s=0;for(let r of i){let a=r.evidence.filter(c=>{try{return new URL(c.location).host.replace(/^www\./,"")===n}catch{return!1}});a.length>0?o.push({...r,evidence:a,occurrences:Math.max(1,a.length)}):s++}return{kept:o,dropped:s}}var m=`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 I(i,e,n){let o=e.pages.map(t=>({url:t.url,status:t.status,title:t.title,metaDescription:t.metaDescription,canonical:t.canonical,h1s:t.h1s,headings:t.headings.slice(0,20).map(l=>`h${l.level}: ${l.text}`),jsonLdTypes:t.jsonLd.length,og:t.og,wordCount:t.wordCount,textSample:t.textSample.slice(0,1500)})),s="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=[`Site: ${e.origin}`,`robots.txt:
|
|
20
|
+
${e.robots.raw.slice(0,2e3)||"(none)"}`,`Crawled pages (JSON):
|
|
21
|
+
${JSON.stringify(o,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
|
+
${f(n)}`,m].join(`
|
|
29
|
+
|
|
30
|
+
`),c=h(await i.analyzeJson({system:s,prompt:a,jsonSchema:g}));return x(c,e.origin).kept}async function j(i,e,n,o,s){let r="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.",a=[`<env>
|
|
31
|
+
Repository root: ${e}
|
|
32
|
+
Detected framework: ${n} (verify this yourself \u2014 detection may be wrong)
|
|
33
|
+
Today's date: ${new Date().toDateString()}
|
|
34
|
+
</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):
|
|
35
|
+
${f(o)}`,m,"Cite evidence as file paths relative to the repository root, pointing at files you actually opened."].join(`
|
|
36
|
+
|
|
37
|
+
`),c=h(await i.exploreJson({cwd:e,system:r,prompt:a,jsonSchema:g,onEvent:s})),{kept:t,dropped:l}=b(c,e);return l>0&&s?.({kind:"text",message:`Discarded ${l} AI finding(s) with unverifiable evidence`}),t}export{b as a,x as b,I as c,j as d};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import{b as u}from"./chunk-UY6CGWIN.js";import y from"fs";import p 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(u.pick({category:!0,severity:!0,title:!0,description:!0,recommendation:!0}).extend({evidence:d.array(d.object({location:d.string(),snippet:d.string().optional()}))}))}),w=0;function h(i){return v.parse(i).findings.map(n=>u.parse({...n,id:`ai-${++w}`,ruleId:"ai-audit",occurrences:Math.max(1,n.evidence.length),source:"ai"}))}function f(i){return i.map(e=>`- ${e.ruleId}: ${e.title}`).join(`
|
|
3
|
+
`)}function b(i,e){let n=[],o=0;for(let s of i){let r=s.evidence.filter(a=>{let c=a.location.replace(/^\.\//,""),t=p.isAbsolute(c)?c:p.join(e,c);return t.startsWith(p.resolve(e))&&y.existsSync(t)});r.length>0?n.push({...s,evidence:r,occurrences:Math.max(1,r.length)}):o++}return{kept:n,dropped:o}}function x(i,e){let n=new URL(e).host.replace(/^www\./,""),o=[],s=0;for(let r of i){let a=r.evidence.filter(c=>{try{return new URL(c.location).host.replace(/^www\./,"")===n}catch{return!1}});a.length>0?o.push({...r,evidence:a,occurrences:Math.max(1,a.length)}):s++}return{kept:o,dropped:s}}var m=`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 I(i,e,n){let o=e.pages.map(t=>({url:t.url,status:t.status,title:t.title,metaDescription:t.metaDescription,canonical:t.canonical,h1s:t.h1s,headings:t.headings.slice(0,20).map(l=>`h${l.level}: ${l.text}`),jsonLdTypes:t.jsonLd.length,og:t.og,wordCount:t.wordCount,textSample:t.textSample.slice(0,1500)})),s="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=[`Site: ${e.origin}`,`robots.txt:
|
|
20
|
+
${e.robots.raw.slice(0,2e3)||"(none)"}`,`Crawled pages (JSON):
|
|
21
|
+
${JSON.stringify(o,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
|
+
${f(n)}`,m].join(`
|
|
29
|
+
|
|
30
|
+
`),c=h(await i.analyzeJson({system:s,prompt:a,jsonSchema:g}));return x(c,e.origin).kept}async function j(i,e,n,o,s){let r="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.",a=[`<env>
|
|
31
|
+
Repository root: ${e}
|
|
32
|
+
Detected framework: ${n} (verify this yourself \u2014 detection may be wrong)
|
|
33
|
+
Today's date: ${new Date().toDateString()}
|
|
34
|
+
</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):
|
|
35
|
+
${f(o)}`,m,"Cite evidence as file paths relative to the repository root, pointing at files you actually opened."].join(`
|
|
36
|
+
|
|
37
|
+
`),c=h(await i.exploreJson({cwd:e,system:r,prompt:a,jsonSchema:g,onEvent:s})),{kept:t,dropped:l}=b(c,e);return l>0&&s?.({kind:"text",message:`Discarded ${l} AI finding(s) with unverifiable evidence`}),t}export{b as a,x as b,I as c,j as d};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import{c as i}from"./chunk-4NKZE24G.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,31 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import{b as l}from"./chunk-SPX2RA5T.js";import m 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(l.pick({category:!0,severity:!0,title:!0,description:!0,recommendation:!0}).extend({evidence:d.array(d.object({location:d.string(),snippet:d.string().optional()}))}))}),w=0;function h(i){return v.parse(i).findings.map(n=>l.parse({...n,id:`ai-${++w}`,ruleId:"ai-audit",occurrences:Math.max(1,n.evidence.length),source:"ai"}))}function f(i){return i.map(t=>`- ${t.ruleId}: ${t.title}`).join(`
|
|
3
|
+
`)}function b(i,t){let n=[],s=0;for(let r of i){let o=r.evidence.filter(a=>{let e=a.location.replace(/^\.\//,""),c=u.isAbsolute(e)?e:u.join(t,e);return c.startsWith(u.resolve(t))&&m.existsSync(c)});o.length>0?n.push({...r,evidence:o,occurrences:Math.max(1,o.length)}):s++}return{kept:n,dropped:s}}function x(i,t){let n=new URL(t).host.replace(/^www\./,""),s=[],r=0;for(let o of i){let a=o.evidence.filter(e=>{try{return new URL(e.location).host.replace(/^www\./,"")===n}catch{return!1}});a.length>0?s.push({...o,evidence:a,occurrences:Math.max(1,a.length)}):r++}return{kept:s,dropped:r}}var y=`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 E(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)})),r="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:
|
|
20
|
+
${t.robots.raw.slice(0,2e3)||"(none)"}`,`Crawled pages (JSON):
|
|
21
|
+
${JSON.stringify(s,null,1)}`,`Rule findings already reported (do NOT repeat these):
|
|
22
|
+
${f(n)}`,y].join(`
|
|
23
|
+
|
|
24
|
+
`),a=h(await i.analyzeJson({system:r,prompt:o,jsonSchema:g}));return x(a,t.origin).kept}async function F(i,t,n,s,r){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). 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.",a=[`<env>
|
|
25
|
+
Repository root: ${t}
|
|
26
|
+
Detected framework: ${n} (verify this yourself \u2014 detection may be wrong)
|
|
27
|
+
Today's date: ${new Date().toDateString()}
|
|
28
|
+
</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):
|
|
29
|
+
${f(s)}`,y,"Cite evidence as file paths relative to the repository root, pointing at files you actually opened."].join(`
|
|
30
|
+
|
|
31
|
+
`),e=h(await i.exploreJson({cwd:t,system:o,prompt:a,jsonSchema:g,onEvent:r})),{kept:c,dropped:p}=b(e,t);return p>0&&r?.({kind:"text",message:`Discarded ${p} AI finding(s) with unverifiable evidence`}),c}export{b as a,x as b,E as c,F as d};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import{c as i}from"./chunk-UY6CGWIN.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,42 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import{b as u}from"./chunk-4NKZE24G.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 m(r){return x.parse(r).findings.map(s=>u.parse({...s,id:`ai-${++y}`,ruleId:"ai-audit",occurrences:Math.max(1,s.evidence.length),source:"ai"}))}function w(r){return r.map(e=>`- ${e.ruleId}: ${e.title}`).join(`
|
|
3
|
+
`)}function A(r,e){let s=[],i=0;for(let n of r){let a=n.evidence.filter(o=>{let c=o.location.replace(/^\.\//,""),t=g.isAbsolute(c)?c:g.join(e,c);return t.startsWith(g.resolve(e))&&b.existsSync(t)});a.length>0?s.push({...n,evidence:a,occurrences:Math.max(1,a.length)}):i++}return{kept:s,dropped:i}}function f(r,e){let s=new URL(e).host.replace(/^www\./,""),i=[],n=0;for(let a of r){let o=a.evidence.filter(c=>{try{return new URL(c.location).host.replace(/^www\./,"")===s}catch{return!1}});o.length>0?i.push({...a,evidence:o,occurrences:Math.max(1,o.length)}):n++}return{kept:i,dropped:n}}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 S(r){let e=[],s=i=>new Set(i.toLowerCase().split(/\W+/).filter(n=>n.length>2));for(let i of r){let n=s(i.title);e.some(o=>{let c=s(o.title),t=0;for(let l of n)c.has(l)&&t++;return t/Math.max(1,Math.min(n.size,c.size))>.6})||e.push(i)}return e}var k=[{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."}],I=15;async function F(r,e,s){let i=e.pages.map(t=>({url:t.url,status:t.status,title:t.title,metaDescription:t.metaDescription,canonical:t.canonical,h1s:t.h1s,headings:t.headings.slice(0,20).map(l=>`h${l.level}: ${l.text}`),jsonLdTypes:t.jsonLd.length,og:t.og,wordCount:t.wordCount,textSample:t.textSample.slice(0,1500)})),n="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.`,o=t=>[`Site: ${e.origin}`,`robots.txt:
|
|
26
|
+
${e.robots.raw.slice(0,2e3)||"(none)"}`,`Crawled pages (JSON):
|
|
27
|
+
${JSON.stringify(i,null,1)}`,a,t,`Rule findings already reported (do NOT repeat these):
|
|
28
|
+
${w(s)}`,v].filter(Boolean).join(`
|
|
29
|
+
|
|
30
|
+
`);if(e.pages.length>=I){let t=await Promise.allSettled(k.map(d=>r.analyzeJson({system:n,prompt:o(d.focus),jsonSchema:h}))),l=t.flatMap(d=>d.status==="fulfilled"?m(d.value):[]);if(l.length===0&&t.every(d=>d.status==="rejected"))throw t[0].reason;return f(S(l),e.origin).kept}let c=m(await r.analyzeJson({system:n,prompt:o(),jsonSchema:h}));return f(c,e.origin).kept}var E={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 R(r,e){let s=e.pages.find(n=>{let a=new URL(n.url).pathname.toLowerCase();return/pricing|plans|prices/.test(a)&&n.wordCount>30&&n.status<300});if(!s)return[];let i=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: ${s.url}
|
|
31
|
+
|
|
32
|
+
Page text:
|
|
33
|
+
${s.textSample}
|
|
34
|
+
|
|
35
|
+
What are the plans and their prices? Report whether this is completely answerable from the text.`,jsonSchema:E});return i.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: ${i.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:i.plans.length>0?`Only ${i.plans.length} plan(s) were partially extractable (${i.plans.map(n=>`${n.name}: ${n.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:s.url}],occurrences:1,source:"ai"})]}async function T(r,e,s,i,n){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: ${s} (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
|
+
${w(i)}`,v,"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:h,onEvent:n})),{kept:t,dropped:l}=A(c,e);return l>0&&n?.({kind:"text",message:`Discarded ${l} AI finding(s) with unverifiable evidence`}),t}export{A as a,f as b,S as c,F as d,R as e,T as f};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import{a as T,i as o}from"./chunk-AIOAF3VX.js";import R from"fs";import f from"path";import O from"better-sqlite3";import s from"fs";import u from"path";import m from"os";var i=u.join(m.homedir(),".eternify"),d=u.join(i,"config.json"),l=u.join(i,"reports");function c(){s.mkdirSync(l,{recursive:!0})}function L(){try{let r=s.readFileSync(d,"utf8");return T.parse(JSON.parse(r))}catch{return null}}function I(r){c();let t=new Date().toISOString(),e={...r,createdAt:r.createdAt??t,updatedAt:t};s.writeFileSync(d,JSON.stringify(e,null,2)+`
|
|
3
|
+
`,{mode:384}),s.chmodSync(d,384)}function g(r){switch(r.type){case"codebase":return r.path;case"content-brief":return`${r.keyword} \u2014 ${r.domain}`;default:return r.domain}}function A(r){return"scorecard"in r?r.scorecard.overall:r.type==="ai-visibility"?r.visibilityScore:null}var y=class{db;constructor(t,e){R.mkdirSync(f.dirname(t),{recursive:!0}),this.db=new O(t),this.db.pragma("journal_mode = WAL"),this.db.exec(`
|
|
4
|
+
CREATE TABLE IF NOT EXISTS reports (
|
|
5
|
+
id TEXT PRIMARY KEY,
|
|
6
|
+
type TEXT NOT NULL,
|
|
7
|
+
subject TEXT NOT NULL,
|
|
8
|
+
created_at TEXT NOT NULL,
|
|
9
|
+
overall INTEGER,
|
|
10
|
+
data TEXT NOT NULL
|
|
11
|
+
);
|
|
12
|
+
CREATE INDEX IF NOT EXISTS idx_reports_created ON reports(created_at DESC);
|
|
13
|
+
CREATE INDEX IF NOT EXISTS idx_reports_type_subject ON reports(type, subject);
|
|
14
|
+
CREATE TABLE IF NOT EXISTS kv (
|
|
15
|
+
key TEXT PRIMARY KEY,
|
|
16
|
+
value TEXT NOT NULL,
|
|
17
|
+
updated_at TEXT NOT NULL
|
|
18
|
+
);
|
|
19
|
+
`),e&&this.importLegacyJson(e)}importLegacyJson(t){let e;try{e=R.readdirSync(t).filter(p=>p.endsWith(".json"))}catch{return}if(e.length===0)return;let n=this.db.prepare("SELECT 1 FROM reports WHERE id = ?");for(let p of e)try{let S=o.parse(JSON.parse(R.readFileSync(f.join(t,p),"utf8")));n.get(S.id)||this.saveReport(S)}catch{}}saveReport(t){this.db.prepare("INSERT OR REPLACE INTO reports (id, type, subject, created_at, overall, data) VALUES (?, ?, ?, ?, ?, ?)").run(t.id,t.type,g(t),t.createdAt,A(t),JSON.stringify(t))}loadReport(t){let e=this.db.prepare("SELECT data FROM reports WHERE id = ?").get(t);if(!e)return null;try{return o.parse(JSON.parse(e.data))}catch{return null}}listReportSummaries(){return this.db.prepare("SELECT id, type, subject, created_at, overall FROM reports ORDER BY created_at DESC").all().map(e=>({id:e.id,type:e.type,subject:e.subject,createdAt:e.created_at,overall:e.overall??void 0}))}listReports(){let t=this.db.prepare("SELECT data FROM reports ORDER BY created_at DESC").all(),e=[];for(let n of t)try{e.push(o.parse(JSON.parse(n.data)))}catch{}return e}findLatestDomainReport(t){let e=this.db.prepare("SELECT data FROM reports WHERE type = 'domain' AND subject = ? ORDER BY created_at DESC LIMIT 1").get(t);if(!e)return null;try{return o.parse(JSON.parse(e.data))}catch{return null}}deleteReport(t){return this.db.prepare("DELETE FROM reports WHERE id = ?").run(t).changes>0}kvGet(t,e){let n=this.db.prepare("SELECT value, updated_at FROM kv WHERE key = ?").get(t);if(!n||e!==void 0&&Date.now()-Date.parse(n.updated_at)>e)return null;try{return JSON.parse(n.value)}catch{return null}}kvSet(t,e){this.db.prepare("INSERT OR REPLACE INTO kv (key, value, updated_at) VALUES (?, ?, ?)").run(t,JSON.stringify(e),new Date().toISOString())}close(){this.db.close()}},E=null;function a(){return E||(c(),E=new y(f.join(i,"data.db"),l)),E}var F=r=>a().saveReport(r),X=r=>/^[a-zA-Z0-9-]+$/.test(r)?a().loadReport(r):null,k=()=>a().listReports(),J=()=>a().listReportSummaries(),M=r=>/^[a-zA-Z0-9-]+$/.test(r)?a().deleteReport(r):!1;export{d as a,L as b,I as c,y as d,a as e,F as f,X as g,k as h,J as i,M as j};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import{b as u}from"./chunk-UW3BLXEX.js";import y from"fs";import p 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(u.pick({category:!0,severity:!0,title:!0,description:!0,recommendation:!0}).extend({evidence:d.array(d.object({location:d.string(),snippet:d.string().optional()}))}))}),w=0;function h(i){return v.parse(i).findings.map(n=>u.parse({...n,id:`ai-${++w}`,ruleId:"ai-audit",occurrences:Math.max(1,n.evidence.length),source:"ai"}))}function f(i){return i.map(e=>`- ${e.ruleId}: ${e.title}`).join(`
|
|
3
|
+
`)}function b(i,e){let n=[],o=0;for(let s of i){let r=s.evidence.filter(a=>{let c=a.location.replace(/^\.\//,""),t=p.isAbsolute(c)?c:p.join(e,c);return t.startsWith(p.resolve(e))&&y.existsSync(t)});r.length>0?n.push({...s,evidence:r,occurrences:Math.max(1,r.length)}):o++}return{kept:n,dropped:o}}function x(i,e){let n=new URL(e).host.replace(/^www\./,""),o=[],s=0;for(let r of i){let a=r.evidence.filter(c=>{try{return new URL(c.location).host.replace(/^www\./,"")===n}catch{return!1}});a.length>0?o.push({...r,evidence:a,occurrences:Math.max(1,a.length)}):s++}return{kept:o,dropped:s}}var m=`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 I(i,e,n){let o=e.pages.map(t=>({url:t.url,status:t.status,title:t.title,metaDescription:t.metaDescription,canonical:t.canonical,h1s:t.h1s,headings:t.headings.slice(0,20).map(l=>`h${l.level}: ${l.text}`),jsonLdTypes:t.jsonLd.length,og:t.og,wordCount:t.wordCount,textSample:t.textSample.slice(0,1500)})),s="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=[`Site: ${e.origin}`,`robots.txt:
|
|
20
|
+
${e.robots.raw.slice(0,2e3)||"(none)"}`,`Crawled pages (JSON):
|
|
21
|
+
${JSON.stringify(o,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
|
+
${f(n)}`,m].join(`
|
|
29
|
+
|
|
30
|
+
`),c=h(await i.analyzeJson({system:s,prompt:a,jsonSchema:g}));return x(c,e.origin).kept}async function j(i,e,n,o,s){let r="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.",a=[`<env>
|
|
31
|
+
Repository root: ${e}
|
|
32
|
+
Detected framework: ${n} (verify this yourself \u2014 detection may be wrong)
|
|
33
|
+
Today's date: ${new Date().toDateString()}
|
|
34
|
+
</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):
|
|
35
|
+
${f(o)}`,m,"Cite evidence as file paths relative to the repository root, pointing at files you actually opened."].join(`
|
|
36
|
+
|
|
37
|
+
`),c=h(await i.exploreJson({cwd:e,system:r,prompt:a,jsonSchema:g,onEvent:s})),{kept:t,dropped:l}=b(c,e);return l>0&&s?.({kind:"text",message:`Discarded ${l} AI finding(s) with unverifiable evidence`}),t}export{b as a,x as b,I as c,j as d};
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import*as S from"cheerio";var _="EternifySEOAudit/0.1 (+https://eternifylabs.com)";function A(i){return i?i.match(/<([^>]+)>\s*;\s*rel="?canonical"?/i)?.[1]:void 0}async function k(i,n){let t=Date.now();try{let r=await fetch(i,{headers:{"user-agent":_,accept:"text/html,application/xhtml+xml,application/xml,text/plain,*/*"},redirect:"follow",signal:AbortSignal.timeout(n)}),l=await r.text();return{status:r.status,text:l,contentType:r.headers.get("content-type")??"",finalUrl:r.url,ms:Date.now()-t,xRobotsTag:r.headers.get("x-robots-tag")??void 0,headerCanonical:A(r.headers.get("link"))}}catch{return null}}function E(i){let n=i.trim();/^https?:\/\//i.test(n)||(n=`https://${n}`);let t=new URL(n);return`${t.protocol}//${t.host}`}function $(i,n){try{let t=new URL(i,n);return/^https?:$/.test(t.protocol)?(t.hash="",t.toString()):null}catch{return null}}function v(i){let n=[],t=[],r=[],l=!1;for(let u of i.split(/\r?\n/)){let f=u.replace(/#.*$/,"").trim().match(/^([A-Za-z-]+)\s*:\s*(.*)$/);if(!f)continue;let m=f[1].toLowerCase(),h=f[2].trim();m==="sitemap"&&h?n.push(h):m==="user-agent"?(l&&(r=[]),r.push(h),l=!1):m==="disallow"?(l=!0,h==="/"&&t.push(...r)):m==="allow"&&(l=!0)}return{sitemaps:n,blockedAgents:t}}function M(i,n){let t=[],r=/<loc>\s*([^<]+?)\s*<\/loc>/g,l;for(;(l=r.exec(i))!==null;){let u=$(l[1],n);u&&t.push(u)}return t}function N(i,n){let t=S.load(n),r=new URL(i).origin,l=[];t("h1, h2, h3, h4, h5, h6").each((g,s)=>{l.push({level:Number(s.tagName[1]),text:t(s).text().trim().slice(0,200)})});let u=[];t("img").each((g,s)=>{let c=t(s).attr("src")??t(s).attr("data-src")??"",d=t(s).attr("alt");u.push({src:c.slice(0,300),alt:d===void 0?void 0:d})});let w=new Set,f=[],m=0;t("a[href]").each((g,s)=>{let c=$(t(s).attr("href"),i);c&&(c.startsWith(r)?(w.add(c),f.length<300&&f.push({href:c,anchor:t(s).text().replace(/\s+/g," ").trim().slice(0,120)})):m++)});let h=[],P=0;t('script[type="application/ld+json"]').each((g,s)=>{try{h.push(JSON.parse(t(s).text()))}catch{P++}});let y={},x={};t("meta").each((g,s)=>{let c=t(s).attr("property")??t(s).attr("name")??"",d=t(s).attr("content")??"";c.startsWith("og:")&&(y[c.slice(3)]=d),c.startsWith("twitter:")&&(x[c.slice(8)]=d)});let L=[];t('link[rel="alternate"][hreflang]').each((g,s)=>{L.push({lang:t(s).attr("hreflang"),href:t(s).attr("href")??""})});let b=JSON.stringify(h),C=!!t('meta[name="author"]').attr("content")||t('[rel="author"], .author, .byline, [itemprop="author"]').length>0||/"author"\s*:/.test(b),D=[t('meta[property="article:modified_time"]').attr("content"),t('meta[property="article:published_time"]').attr("content"),(b.match(/"dateModified"\s*:\s*"([^"]+)"/)??[])[1],(b.match(/"datePublished"\s*:\s*"([^"]+)"/)??[])[1]].filter(g=>!!g&&!Number.isNaN(Date.parse(g)))[0],a=t("table").length,o=t("main, article, [role='main']").length>0;t("script, style, noscript, svg").remove();let e=t("body").text().replace(/\s+/g," ").trim(),T=t("p").map((g,s)=>t(s).text().replace(/\s+/g," ").trim()).get().find(g=>g.length>30),p=[];return t("h2, h3").each((g,s)=>{let c=0,d=t(s).next();for(;d.length>0&&!/^h[1-6]$/i.test(d.prop("tagName")??"");){let U=d.text().replace(/\s+/g," ").trim();U&&(c+=U.split(" ").length),d=d.next()}c>0&&p.push(c)}),{url:i,title:t("head title").first().text().trim()||void 0,metaDescription:t('head meta[name="description"]').attr("content")?.trim()||void 0,canonical:t('head link[rel="canonical"]').attr("href")?.trim()||void 0,metaRobots:t('head meta[name="robots"]').attr("content")?.trim()||void 0,lang:t("html").attr("lang")?.trim()||void 0,viewport:t('head meta[name="viewport"]').attr("content")?.trim()||void 0,h1s:t("h1").map((g,s)=>t(s).text().trim()).get(),headings:l,images:u,internalLinks:[...w],internalLinkDetails:f,externalLinks:m,jsonLd:h,jsonLdErrors:P,og:y,twitter:x,hreflang:L,wordCount:e?e.split(" ").length:0,textSample:e.slice(0,2500),firstParagraph:T?.slice(0,500),sectionWordCounts:p,hasAuthorSignal:C,contentDate:D,tableCount:a,hasMainLandmark:o}}async function j(i,n){let t=E(i),[r,l,u]=await Promise.all([k(`${t}/robots.txt`,n.timeoutMs),k(`${t}/sitemap.xml`,n.timeoutMs),k(`${t}/llms.txt`,n.timeoutMs)]),w=r&&r.status===200?r.text:"",f=v(w),m={exists:!!r&&r.status===200,raw:w.slice(0,1e4),sitemaps:f.sitemaps,blockedAgents:f.blockedAgents},h=[],P=!1,y=[...l&&l.status===200&&l.text.includes("<")?[l.text]:[]];if(y.length===0&&m.sitemaps.length>0){let a=await k(m.sitemaps[0],n.timeoutMs);a&&a.status===200&&y.push(a.text)}for(let a of y){P=!0;let o=M(a,t);if(a.includes("<sitemapindex")){let e=o.slice(0,3);o=[];for(let T of e){let p=await k(T,n.timeoutMs);p&&p.status===200&&o.push(...M(p.text,t))}}h=o.filter(e=>e.startsWith(t))}let x=null;if(n.render){let{createRenderer:a}=await import("./render-CCORCUAB.js");x=await a()}let L=async a=>{if(x){let o=Date.now(),e=await x.render(a,n.timeoutMs);if(e)return{status:e.status,text:e.html,contentType:e.headers["content-type"]??"text/html",finalUrl:e.finalUrl,ms:Date.now()-o,xRobotsTag:e.headers["x-robots-tag"],headerCanonical:A(e.headers.link??null)}}return k(a,n.timeoutMs)},b=[t,...h],C=new Set,R=[],D=[];try{for(;b.length>0&&R.length<n.maxPages;){let a=[];for(;a.length<4&&b.length>0&&R.length+a.length<n.maxPages;){let o=b.shift(),e=o.replace(/\/$/,"");C.has(e)||(C.add(e),a.push(o))}if(a.length===0)break;await Promise.all(a.map(async o=>{let e=await L(o);if(!e){D.push({url:o,reason:"fetch failed or timed out"});return}if(!e.contentType.includes("html")){D.push({url:o,reason:`not HTML (${e.contentType||"unknown"})`});return}let T=N(e.finalUrl,e.text);R.push({...T,url:o,redirectedTo:e.finalUrl!==o?e.finalUrl:void 0,status:e.status,contentType:e.contentType,fetchTimeMs:e.ms,xRobotsTag:e.xRobotsTag,headerCanonical:e.headerCanonical,rendered:!!x});for(let p of T.internalLinks)C.has(p.replace(/\/$/,""))||b.push(p);n.onProgress?.(R.length,n.maxPages,o)}))}}finally{await x?.close()}return{origin:t,robots:m,sitemapExists:P,sitemapUrls:h,llmsTxtExists:!!u&&u.status===200&&!u.contentType.includes("html"),pages:R,failedUrls:D}}export{E as a,N as b,j as c};
|
|
@@ -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"]),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()}),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,16 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import{c as i}from"./chunk-AIOAF3VX.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,50 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import{b as u}from"./chunk-AIOAF3VX.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,tier:"light"}),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,tier:"light"});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,2 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import{z as e}from"zod";var n=e.enum(["claude","openai","gemini"]),x=e.object({provider:n,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"]),p=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(p).default([]),occurrences:e.number().int().default(1),source:e.enum(["rule","ai"]).default("rule")}),s=e.object({category:t,label:e.string(),score:e.number().min(0).max(100)}),o=e.object({overall:e.number().min(0).max(100),categories:e.array(s)}),c=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"])}),i=e.object({summary:e.string(),strengths:e.array(e.string()),actionPlan:e.array(c)}),g=e.object({url:e.string(),status:e.number(),title:e.string().optional(),metaDescription:e.string().optional(),h1:e.string().optional(),wordCount:e.number()}),d=e.object({id:e.string(),type:e.literal("domain"),domain:e.string(),createdAt:e.string(),pagesCrawled:e.number(),pages:e.array(g),scorecard:o,findings:e.array(r),llmReport:i.optional()}),y=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()}),m=e.object({id:e.string(),type:e.literal("keywords"),auditId:e.string(),domain:e.string(),createdAt:e.string(),keywords:e.array(y),gaps:e.array(e.object({keyword:e.string(),rationale:e.string()})),summary:e.string()}),l=e.object({id:e.string(),type:e.literal("codebase"),path:e.string(),framework:e.string(),createdAt:e.string(),filesScanned:e.number(),scorecard:o,findings:e.array(r),llmReport:i.optional()}),u=e.object({prompt:e.string(),presence:e.enum(["primary","mentioned","absent"]),answerExcerpt:e.string(),competitors:e.array(e.string())}),f=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()})),summary:e.string()}),A=e.discriminatedUnion("type",[d,m,l,f]);export{x as a,r as b,i as c,d,m as e,l as f,f as g,A as h};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import{c as i}from"./chunk-U3KERC3T.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 i=e.enum(["claude","openai","gemini"]),v=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"),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)}),j=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,j as i};
|
|
@@ -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)}),o=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"])}),n=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:o,findings:e.array(r),llmReport:n.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()}),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:o,findings:e.array(r),llmReport:n.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,n 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 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)}),o=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"])}),n=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()}),d=e.object({id:e.string(),type:e.literal("domain"),domain:e.string(),createdAt:e.string(),pagesCrawled:e.number(),pages:e.array(c),scorecard:o,findings:e.array(r),llmReport:n.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()}),y=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()}),l=e.object({id:e.string(),type:e.literal("codebase"),path:e.string(),framework:e.string(),createdAt:e.string(),filesScanned:e.number(),scorecard:o,findings:e.array(r),llmReport:n.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,y,l,b]);export{A as a,r as b,n as c,d,y as e,l 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 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"]),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()}),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(),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()}),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};
|