@eternify-labs/seo-audit 0.3.1 → 0.4.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/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
- Eternify Labs SEO/AEO Audit — End User License Agreement
1
+ Pulse Media Agency SEO/AEO Audit — End User License Agreement
2
2
 
3
- Copyright (c) 2026 Eternify Labs. All rights reserved.
3
+ Copyright (c) 2026 Pulse Media Agency. All rights reserved.
4
4
 
5
5
  This software is proprietary. It is provided free of charge for use under the
6
6
  following terms:
@@ -26,7 +26,7 @@ following terms:
26
26
  EXPRESS OR IMPLIED. AUDIT RESULTS AND AUTOMATED CODE CHANGES ARE ADVISORY;
27
27
  REVIEW ALL CHANGES BEFORE DEPLOYING THEM.
28
28
 
29
- 5. LIMITATION OF LIABILITY. IN NO EVENT SHALL ETERNIFY LABS BE LIABLE FOR ANY
29
+ 5. LIMITATION OF LIABILITY. IN NO EVENT SHALL PULSE MEDIA AGENCY BE LIABLE FOR ANY
30
30
  CLAIM, DAMAGES, OR OTHER LIABILITY ARISING FROM THE USE OF THE SOFTWARE.
31
31
 
32
32
  6. TERMINATION. This license terminates automatically if you breach these
package/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # Eternify SEO/AEO Audit
1
+ # Pulse Media SEO/AEO Audit
2
2
 
3
3
  CLI tool with a local web UI that audits **domains** and **codebases** for SEO
4
4
  (search engine optimization) and AEO (answer engine optimization — visibility in
@@ -99,13 +99,13 @@ Every audit runs in **two rounds**:
99
99
  ## CLI
100
100
 
101
101
  ```
102
- eternify-seo # start the web UI (default)
103
- eternify-seo login # (re)configure provider / auth / model
104
- eternify-seo audit <domain> # headless domain audit (--pages N, --no-ai, --json)
105
- eternify-seo scan [path] # headless codebase audit (--no-ai, --json)
106
- eternify-seo drift <domain> # re-audit + compare vs previous run; exit 1 on
107
- # regressions (--fail-on critical|warning|info)
108
- eternify-seo config # show current configuration
102
+ pulse-seo # start the web UI (default)
103
+ pulse-seo login # (re)configure provider / auth / model
104
+ pulse-seo audit <domain> # headless domain audit (--pages N, --no-ai, --json, --pdf)
105
+ pulse-seo scan [path] # headless codebase audit (--no-ai, --json, --pdf)
106
+ pulse-seo drift <domain> # re-audit + compare vs previous run; exit 1 on
107
+ # regressions (--fail-on critical|warning|info)
108
+ pulse-seo config # show current configuration
109
109
  ```
110
110
 
111
111
  Scores come from one penalty engine applied to both rounds' findings — the AI
@@ -120,18 +120,22 @@ With `--no-ai` only round 1 runs and the score is purely rule-based.
120
120
  env: { ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY } } }
121
121
  ```
122
122
 
123
- Or in cron for scheduled monitoring: `0 6 * * * eternify-seo drift yourdomain.com`
123
+ Or in cron for scheduled monitoring: `0 6 * * * pulse-seo drift yourdomain.com`
124
124
 
125
- Config lives in `~/.eternify/config.json` (chmod 600); reports and caches in
126
- a local SQLite database at `~/.eternify/data.db`. Everything runs locally —
125
+ Config lives in `~/.pulsemedia/config.json` (chmod 600); reports and caches in
126
+ a local SQLite database at `~/.pulsemedia/data.db`. Everything runs locally —
127
127
  page content is sent only to the AI provider you chose.
128
128
 
129
+ Every report exports as a Pulse Media Agency branded PDF: use **Download PDF** in
130
+ the UI, or add `--pdf [file]` to `audit` and `scan`. The PDF is printed by your
131
+ installed Google Chrome (or Edge), the same browser `--render` uses.
132
+
129
133
  **Economy mode** (default on): mechanical tasks — visibility classification,
130
134
  the dissenter lens, paste tests, SERP-type classification — automatically run
131
135
  on a lighter, cheaper model (Haiku 4.5 / Gemini Flash), while audits, briefs
132
136
  and reports keep your selected model. Toggle in Settings.
133
137
 
134
- ## Development (Eternify Labs internal)
138
+ ## Development (Pulse Media Agency internal)
135
139
 
136
140
  ```
137
141
  npm install
@@ -147,4 +151,4 @@ source stays in this private monorepo.
147
151
 
148
152
  ---
149
153
 
150
- Built by [Krutik Parikh](https://www.krutikparikh.ca/) at [Eternify Labs](https://eternifylabs.com/).
154
+ Built by [Krutik Parikh](https://www.krutikparikh.ca/) for [Pulse Media Agency](https://pulsemediaagency.ca/).
@@ -38,3 +38,12 @@ SOFTWARE.
38
38
  Statistics surfaced in reports cite their original research sources
39
39
  (Princeton GEO / KDD 2024, SE Ranking, Ahrefs, and others), not these
40
40
  repositories.
41
+
42
+ ---
43
+
44
+ **Archivo** and **Inter** typefaces, shipped in `ui/public/fonts` for the UI and
45
+ PDF exports. Licensed under the SIL Open Font License, Version 1.1
46
+ (https://openfontlicense.org).
47
+
48
+ - Archivo: Copyright 2020 The Archivo Project Authors
49
+ - Inter: Copyright 2020 The Inter Project Authors
@@ -0,0 +1,165 @@
1
+ #!/usr/bin/env node
2
+ import{a as u}from"./chunk-O7EAZALL.js";var $=[-40,-160.166,1952.1763848396504,230.166],b="M0.00 -44.00 L67.50 -44.00 L78.00 -62.92 L88.50 -44.00 L135.00 -44.00 L144.00 -111.17 L156.00 23.17 L165.00 -44.00 L211.50 -44.00 L222.00 -62.92 L232.50 -44.00 L300.00 -44.00",Q="M7.49 0.00V-68.71H45.26Q52.26 -68.71 57.21 -65.87Q62.16 -63.04 64.83 -57.97Q67.49 -52.91 67.49 -46.06Q67.49 -39.14 64.75 -34.00Q62.02 -28.87 56.98 -26.04Q51.93 -23.20 44.94 -23.20H25.44V0.00ZM25.44 -37.02H40.74Q44.92 -37.02 47.13 -39.35Q49.33 -41.67 49.33 -45.95Q49.33 -48.85 48.38 -50.79Q47.42 -52.73 45.53 -53.75Q43.64 -54.78 40.74 -54.78H25.44ZM108.44 1.20Q98.47 1.20 91.34 -1.89Q84.22 -4.98 80.41 -11.20Q76.61 -17.42 76.61 -26.65V-68.71H94.55V-27.00Q94.55 -20.40 98.08 -16.59Q101.60 -12.79 108.39 -12.79Q115.17 -12.79 118.77 -16.59Q122.37 -20.40 122.37 -27.00V-68.71H140.31V-26.65Q140.31 -17.42 136.52 -11.20Q132.72 -4.98 125.59 -1.89Q118.47 1.20 108.44 1.20ZM154.59 0.00V-68.71H172.54V-14.72H207.43V0.00ZM244.87 1.20Q238.41 1.20 232.69 0.06Q226.98 -1.08 222.62 -3.68Q218.27 -6.27 215.78 -10.55Q213.29 -14.83 213.29 -21.04Q213.29 -21.42 213.31 -21.89Q213.34 -22.35 213.39 -22.71H230.73Q230.68 -22.40 230.65 -21.96Q230.63 -21.52 230.63 -21.17Q230.63 -18.03 232.26 -16.09Q233.89 -14.15 236.92 -13.28Q239.95 -12.41 243.92 -12.41Q246.08 -12.41 247.98 -12.62Q249.88 -12.83 251.40 -13.31Q252.92 -13.78 254.04 -14.54Q255.16 -15.29 255.74 -16.34Q256.32 -17.40 256.32 -18.79Q256.32 -21.15 254.57 -22.61Q252.82 -24.08 249.86 -25.10Q246.89 -26.13 243.17 -26.99Q239.46 -27.85 235.49 -28.87Q231.52 -29.89 227.81 -31.41Q224.09 -32.93 221.13 -35.22Q218.16 -37.50 216.41 -40.96Q214.67 -44.42 214.67 -49.28Q214.67 -54.73 216.96 -58.66Q219.25 -62.58 223.33 -65.07Q227.41 -67.55 232.71 -68.73Q238.00 -69.91 244.01 -69.91Q249.89 -69.91 255.07 -68.70Q260.25 -67.50 264.22 -64.97Q268.19 -62.44 270.45 -58.55Q272.72 -54.67 272.77 -49.30V-48.10H255.63V-48.78Q255.63 -51.08 254.34 -52.86Q253.05 -54.64 250.56 -55.69Q248.06 -56.73 244.40 -56.73Q240.72 -56.73 238.14 -56.02Q235.57 -55.31 234.25 -53.98Q232.93 -52.66 232.93 -50.91Q232.93 -48.74 234.67 -47.31Q236.42 -45.88 239.39 -44.90Q242.36 -43.92 246.07 -43.08Q249.78 -42.24 253.75 -41.25Q257.72 -40.27 261.44 -38.82Q265.15 -37.38 268.12 -35.13Q271.09 -32.88 272.83 -29.58Q274.58 -26.28 274.58 -21.57Q274.58 -13.43 270.72 -8.41Q266.86 -3.38 260.15 -1.09Q253.43 1.20 244.87 1.20ZM285.75 0.00V-68.71H342.52V-54.67H303.70V-41.80H337.57V-28.14H303.70V-14.03H343.17V0.00Z",y="M8.14 0.00V-68.60H25.57L38.92 -27.04Q39.52 -25.17 40.14 -22.86Q40.75 -20.55 41.30 -18.46Q41.85 -16.37 42.15 -14.84H42.88Q43.15 -16.31 43.61 -18.39Q44.07 -20.47 44.63 -22.78Q45.19 -25.08 45.80 -27.12L59.15 -68.60H76.44V0.00H65.28V-37.59Q65.28 -41.42 65.37 -45.51Q65.46 -49.59 65.59 -52.77Q65.71 -55.95 65.71 -56.94H64.94Q64.66 -55.80 63.92 -53.14Q63.18 -50.48 62.37 -47.49Q61.55 -44.50 60.88 -42.27L47.05 0.00H37.07L23.24 -42.19Q22.64 -44.12 21.94 -46.69Q21.24 -49.27 20.53 -52.00Q19.83 -54.74 19.26 -56.94H18.48Q18.58 -54.46 18.66 -50.99Q18.73 -47.52 18.82 -43.97Q18.91 -40.43 18.91 -37.59V0.00ZM102.74 0.00V-68.60H155.87V-59.03H113.89V-39.87H151.47V-30.30H113.89V-9.57H156.47V0.00ZM180.14 0.00V-68.60H206.64Q217.23 -68.60 224.73 -64.95Q232.23 -61.30 236.25 -53.75Q240.28 -46.20 240.28 -34.30Q240.28 -22.53 236.25 -14.93Q232.23 -7.33 224.73 -3.66Q217.23 0.00 206.64 0.00ZM191.29 -9.57H206.54Q211.80 -9.57 215.93 -10.98Q220.05 -12.39 222.91 -15.28Q225.76 -18.16 227.25 -22.63Q228.75 -27.09 228.75 -33.18V-35.25Q228.75 -41.48 227.25 -45.96Q225.76 -50.44 222.92 -53.32Q220.08 -56.21 215.95 -57.62Q211.83 -59.03 206.54 -59.03H191.29ZM263.24 0.00V-68.60H274.39V0.00ZM293.12 0.00 320.04 -68.60H334.35L361.34 0.00H349.26L343.15 -15.86H310.68L304.60 0.00ZM314.27 -25.36H339.49L331.84 -45.49Q331.46 -46.44 330.90 -48.00Q330.34 -49.56 329.69 -51.48Q329.04 -53.39 328.38 -55.36Q327.72 -57.33 327.17 -58.93H326.59Q325.97 -56.95 325.08 -54.35Q324.20 -51.75 323.35 -49.37Q322.50 -46.98 321.93 -45.49ZM403.12 0.00 430.04 -68.60H444.35L471.34 0.00H459.26L453.15 -15.86H420.68L414.60 0.00ZM424.27 -25.36H449.49L441.84 -45.49Q441.46 -46.44 440.90 -48.00Q440.34 -49.56 439.69 -51.48Q439.04 -53.39 438.38 -55.36Q437.72 -57.33 437.17 -58.93H436.59Q435.97 -56.95 435.08 -54.35Q434.20 -51.75 433.35 -49.37Q432.50 -46.98 431.93 -45.49ZM521.16 1.20Q504.90 1.20 496.17 -7.42Q487.45 -16.05 487.45 -34.30Q487.45 -46.25 491.52 -54.12Q495.60 -61.98 503.41 -65.89Q511.23 -69.80 522.37 -69.80Q529.14 -69.80 534.89 -68.29Q540.65 -66.78 544.95 -63.73Q549.25 -60.68 551.62 -56.08Q553.99 -51.48 553.99 -45.20H542.56Q542.56 -49.08 541.01 -51.94Q539.46 -54.79 536.72 -56.68Q533.97 -58.56 530.36 -59.43Q526.74 -60.31 522.63 -60.31Q516.88 -60.31 512.45 -58.82Q508.02 -57.34 505.02 -54.24Q502.01 -51.14 500.49 -46.44Q498.97 -41.74 498.97 -35.30V-33.20Q498.97 -24.44 501.55 -18.93Q504.13 -13.41 509.18 -10.85Q514.24 -8.29 521.52 -8.29Q528.37 -8.29 533.10 -10.29Q537.83 -12.28 540.33 -16.24Q542.84 -20.20 542.84 -26.08V-27.07H519.72V-36.41H554.01V0.00H545.85L544.80 -8.16Q541.97 -4.92 538.40 -2.84Q534.83 -0.77 530.55 0.21Q526.27 1.20 521.16 1.20ZM579.64 0.00V-68.60H632.77V-59.03H590.79V-39.87H628.37V-30.30H590.79V-9.57H633.37V0.00ZM657.04 0.00V-68.60H667.76L699.15 -25.62Q699.67 -24.99 700.42 -23.88Q701.17 -22.77 701.87 -21.68Q702.56 -20.58 702.91 -19.86H703.39Q703.39 -21.35 703.39 -22.78Q703.39 -24.20 703.39 -25.62V-68.60H714.16V0.00H703.81L672.12 -43.55Q671.34 -44.68 670.14 -46.56Q668.93 -48.44 668.28 -49.43H667.81Q667.81 -47.99 667.81 -46.53Q667.81 -45.07 667.81 -43.55V0.00ZM770.15 1.20Q759.65 1.20 752.27 -2.57Q744.89 -6.35 741.02 -14.21Q737.14 -22.07 737.14 -34.30Q737.14 -52.28 745.87 -61.04Q754.59 -69.80 770.20 -69.80Q778.84 -69.80 785.67 -66.92Q792.49 -64.05 796.46 -58.26Q800.42 -52.47 800.42 -43.59H789.05Q789.05 -49.13 786.70 -52.83Q784.36 -56.54 780.13 -58.42Q775.91 -60.31 770.30 -60.31Q763.28 -60.31 758.42 -57.57Q753.55 -54.84 751.11 -49.30Q748.67 -43.76 748.67 -35.25V-33.08Q748.67 -24.62 751.11 -19.13Q753.55 -13.64 758.38 -10.96Q763.21 -8.29 770.32 -8.29Q776.13 -8.29 780.43 -10.14Q784.74 -11.99 787.09 -15.69Q789.45 -19.40 789.45 -24.93H800.42Q800.42 -16.01 796.45 -10.22Q792.47 -4.43 785.64 -1.61Q778.80 1.20 770.15 1.20ZM842.52 0.00V-28.19L815.80 -68.60H828.61L848.22 -38.19H848.69L868.13 -68.60H880.36L853.69 -28.19V0.00Z";import x from"fs";import p from"path";import{fileURLToPath as j}from"url";var m=p.dirname(j(import.meta.url)),z=[p.join(m,"ui"),p.join(m,"../../dist/ui"),p.join(m,"../../ui/public")];function w(t){for(let e of z){let a=p.join(e,t);if(x.existsSync(a))return x.readFileSync(a)}return null}function s(t){return String(t??"").replace(/[&<>"']/g,e=>({"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#39;"})[e])}var r={critical:"#b42318",warning:"#b54708",info:"#0b5fd0",pass:"#067647"},v={critical:0,warning:1,info:2,pass:3},I=8;function f(t){return t>=80?r.pass:t>=55?r.warning:r.critical}function d(t){switch(t.type){case"codebase":return t.path;case"content-brief":return`${t.keyword} \xB7 ${t.domain}`;default:return t.domain}}var H={domain:"Domain audit",codebase:"Codebase audit",keywords:"Keyword analysis","ai-visibility":"AI visibility","content-brief":"Content brief"};function V(t){let[e,a,n,i]=$;return`<svg width="${Math.round(t*n/i)}" height="${t}" viewBox="${e} ${a} ${n} ${i}" role="img" aria-label="Pulse Media Agency">
3
+ <defs>
4
+ <linearGradient id="pm-trace" gradientUnits="userSpaceOnUse" x1="0" y1="0" x2="300" y2="0"><stop offset="0" stop-color="#0A46C8" stop-opacity="0"/><stop offset=".12" stop-color="#0A46C8" stop-opacity=".75"/><stop offset=".50" stop-color="#0E86E8"/><stop offset=".88" stop-color="#0A46C8" stop-opacity=".75"/><stop offset="1" stop-color="#0A46C8" stop-opacity="0"/></linearGradient>
5
+ <linearGradient id="pm-divider" gradientUnits="objectBoundingBox" x1="0" y1="0" x2="0" y2="1"><stop offset="0" stop-color="#7DF0FF"/><stop offset="1" stop-color="#1B6BFF"/></linearGradient>
6
+ </defs>
7
+ <path d="${b}" fill="none" stroke="url(#pm-trace)" stroke-width="8.28" stroke-linecap="round" stroke-linejoin="round"/>
8
+ <g fill="#0B1524" transform="translate(362 0)"><path d="${Q}" transform="scale(1.45773)"/></g>
9
+ <rect x="915.3" y="-102" width="5.5" height="110" rx="2.7" fill="url(#pm-divider)"/>
10
+ <g fill="#45607a" transform="translate(972.8 0)"><path d="${y}" transform="scale(1.02041)"/></g>
11
+ </svg>`}var c=null;function M(){if(c!==null)return c;let t=(e,a,n="")=>{let i=w(`fonts/${a}`);return i?`@font-face{font-family:"${e}";src:url(data:font/ttf;base64,${i.toString("base64")}) format("truetype");font-weight:100 900;${n}}`:""};return c=t("Archivo","Archivo.ttf","font-stretch:62% 125%;")+t("Inter","Inter.ttf"),c}function A(t,e,a){return`
12
+ <div class="hero">
13
+ <div class="hero-score">
14
+ <div class="score" style="color:${f(t)}">${t}<small>/100</small></div>
15
+ <div class="score-label">${s(e)}</div>
16
+ </div>
17
+ <div class="hero-side">${a}</div>
18
+ </div>`}function R(t){return t.map(e=>`
19
+ <div class="cat">
20
+ <div class="cat-row"><span>${s(e.label)}</span><strong style="color:${f(e.score)}">${e.score}</strong></div>
21
+ <div class="bar"><div style="width:${e.score}%;background:${f(e.score)}"></div></div>
22
+ ${e.note?`<p class="note">${s(e.note)}</p>`:""}
23
+ </div>`).join("")}function L(t){return A(t.overall,"Overall score",R(t.categories))}function S(t){if(!t)return"";let e=t.strengths.length?`<h3>What is already working</h3><ul class="list">${t.strengths.map(n=>`<li>${s(n)}</li>`).join("")}</ul>`:"",a=t.actionPlan.map(n=>`
24
+ <div class="card">
25
+ <div class="head"><span class="prio">${n.priority}</span><strong>${s(n.title)}</strong>
26
+ <span class="muted">Impact ${s(n.impact)} \xB7 Effort ${s(n.effort)}</span></div>
27
+ <p>${s(n.detail)}</p>
28
+ ${n.unblocks?`<p><span class="label">Unblocks:</span> ${s(n.unblocks)}</p>`:""}
29
+ ${n.leadingIndicator?`<p><span class="label">Watch:</span> ${s(n.leadingIndicator)}</p>`:""}
30
+ ${n.failureSignal?`<p><span class="label">Not working if:</span> ${s(n.failureSignal)}</p>`:""}
31
+ </div>`).join("");return`
32
+ <h2>Summary</h2>
33
+ <p class="summary">${s(t.summary)}</p>
34
+ ${e}
35
+ ${a?`<h2>Action plan</h2>${a}`:""}`}function k(t){let e=[...t].sort((i,l)=>(v[i.severity]??9)-(v[l.severity]??9)),a=i=>t.filter(l=>l.severity===i).length,n=e.map(i=>{let l=r[i.severity]??r.info,o=i.evidence.slice(0,I),h=i.evidence.length-o.length;return`
36
+ <div class="card" style="border-left-color:${l}">
37
+ <div class="head">
38
+ <span class="tag" style="color:${l}">${s(i.severity)}</span>
39
+ ${i.source==="ai"?'<span class="tag" style="color:#0846a2">AI</span>':""}
40
+ ${i.disputed?'<span class="tag" style="color:#5a7086">Disputed</span>':""}
41
+ <strong>${s(i.title)}</strong>
42
+ ${i.occurrences>1?`<span class="muted">${i.occurrences} occurrences</span>`:""}
43
+ </div>
44
+ <p>${s(i.description)}</p>
45
+ <p class="fix"><span class="label">Fix:</span> ${s(i.recommendation)}</p>
46
+ ${o.length?`<ul class="evidence">${o.map(g=>`<li>${s(g.location)}${g.snippet?`: ${s(g.snippet)}`:""}</li>`).join("")}${h>0?`<li>and ${h} more</li>`:""}</ul>`:""}
47
+ </div>`}).join("");return`
48
+ <h2>Findings</h2>
49
+ <div class="counts"><span><b style="color:${r.critical}">${a("critical")}</b> critical</span><span><b style="color:${r.warning}">${a("warning")}</b> warnings</span><span><b style="color:${r.info}">${a("info")}</b> suggestions</span></div>
50
+ ${n}`}function D(t){return`
51
+ ${L(t.scorecard)}
52
+ ${S(t.llmReport)}
53
+ ${k(t.findings)}
54
+ <h2>Crawled pages</h2>
55
+ <table><thead><tr><th>URL</th><th>Status</th><th>Title</th><th class="num">Words</th></tr></thead><tbody>
56
+ ${t.pages.map(e=>`<tr><td class="url">${s(e.url)}</td><td style="color:${e.status>=400?r.critical:"inherit"}">${e.status}</td><td>${e.title?s(e.title):'<span class="missing">Missing</span>'}</td><td class="num">${e.wordCount}</td></tr>`).join("")}
57
+ </tbody></table>`}function F(t){return`
58
+ ${L(t.scorecard)}
59
+ ${S(t.llmReport)}
60
+ ${k(t.findings)}`}function E(t){let e={strong:r.pass,partial:r.warning,missing:r.critical},a=t.keywords.map(n=>{let i=[n.notes];return n.serp&&n.serp.mismatch!=="none"&&i.push(`Search results favour ${n.serp.consensusType}; the site offers ${n.serp.sitePageType} (${n.serp.mismatch} mismatch). ${n.serp.advice}`),n.fanOut?.length&&i.push(`Related AI queries covered: ${n.fanOut.filter(l=>l.covered).length} of ${n.fanOut.length}.`),`<tr>
61
+ <td><strong>${s(n.keyword)}</strong>${i.filter(Boolean).map(l=>`<p class="note">${s(l)}</p>`).join("")}</td>
62
+ <td>${s(n.intent)}${n.stage?`<p class="note">${s(n.stage)}</p>`:""}</td>
63
+ <td><span class="tag" style="color:${e[n.coverage]}">${s(n.coverage)}</span>${n.aeoCitable?'<p class="note">AI citable</p>':""}</td>
64
+ <td class="num">${n.searchVolume!==void 0?n.searchVolume.toLocaleString("en-CA"):"\xB7"}</td>
65
+ <td class="url">${n.bestPage?s(n.bestPage):'<span class="missing">None</span>'}</td>
66
+ </tr>`}).join("");return`
67
+ <h2>Summary</h2>
68
+ <p class="summary">${s(t.summary)}</p>
69
+ <h2>Keywords</h2>
70
+ <table><thead><tr><th>Keyword</th><th>Intent</th><th>Coverage</th><th class="num">Volume</th><th>Best page</th></tr></thead><tbody>${a}</tbody></table>
71
+ ${t.gaps.length?`<h2>Content gaps</h2>${t.gaps.map(n=>`<div class="card"><div class="head"><strong>${s(n.keyword)}</strong></div><p>${s(n.rationale)}</p></div>`).join("")}`:""}`}function O(t){return`
72
+ <div class="callout"><span class="label">Information gain.</span> Why this page deserves to exist: ${s(t.informationGain)}</div>
73
+ <p class="muted" style="margin-top:12px">Format: ${s(t.format)}${t.competitorName?` \xB7 vs ${s(t.competitorName)}`:""} \xB7 Intent: ${s(t.searchIntent)} \xB7 Page type: ${s(t.recommendedPageType)}</p>
74
+ <h2>Title and meta description</h2>
75
+ <p><strong>${s(t.titleSuggestion)}</strong> <span class="muted">(${t.titleSuggestion.length} characters)</span></p>
76
+ <p style="margin-top:6px">${s(t.metaDescription)} <span class="muted">(${t.metaDescription.length} characters)</span></p>
77
+ <h2>Outline</h2>
78
+ ${t.outline.map(e=>`<div class="card"><div class="head"><strong>${s(e.heading)}</strong><span class="muted">about ${e.targetWords} words</span></div>
79
+ <ul class="list">${e.keyPoints.map(a=>`<li>${s(a)}</li>`).join("")}</ul></div>`).join("")}
80
+ ${t.competitors.length?`<h2>Ranking competitors</h2><table><thead><tr><th>Domain</th><th class="num">Depth</th><th class="num">Format</th><th class="num">SEO</th><th class="num">UX</th><th>Beat them by</th></tr></thead><tbody>
81
+ ${t.competitors.map(e=>`<tr><td class="url">${s(e.domain)}</td><td class="num">${e.scores.depth}</td><td class="num">${e.scores.formatting}</td><td class="num">${e.scores.seo}</td><td class="num">${e.scores.ux}</td><td>${s(e.weakness)}</td></tr>`).join("")}</tbody></table>`:""}
82
+ ${t.faq.length?`<h2>FAQ</h2>${t.faq.map(e=>`<div class="card"><div class="head"><strong>${s(e.question)}</strong></div><p>${s(e.answerAngle)}</p></div>`).join("")}`:""}
83
+ ${t.internalLinks.length?`<h2>Internal links</h2><ul class="evidence">${t.internalLinks.map(e=>`<li>${s(e)}</li>`).join("")}</ul>`:""}`}function P(t){let e=t.prompts.filter(o=>o.presence==="recommended"||o.presence==="primary").length,a=t.prompts.filter(o=>o.presence==="mentioned").length,n=t.prompts.filter(o=>o.recommendedAgainst).length,i=o=>o==="absent"?r.critical:o==="mentioned"?r.warning:r.pass,l=`
84
+ <div class="counts"><span><b style="color:${r.pass}">${e}</b> recommended</span><span><b style="color:${r.warning}">${a}</b> mentioned</span><span><b style="color:${r.critical}">${t.prompts.length-e-a}</b> absent</span>${n?`<span><b style="color:${r.critical}">${n}</b> recommended against</span>`:""}</div>
85
+ <p class="summary" style="font-size:10pt">${s(t.summary)}</p>`;return`
86
+ ${A(t.visibilityScore,"Visibility score",l)}
87
+ ${t.engineReadiness?.length?`<h2>Readiness by engine</h2>${R(t.engineReadiness.map(o=>({label:o.engine,score:o.score,note:o.notes.length?`Held back by: ${o.notes.join(" \xB7 ")}`:void 0})))}`:""}
88
+ <h2>Prompt results</h2>
89
+ ${t.prompts.map(o=>`<div class="card" style="border-left-color:${i(o.presence)}"><div class="head">
90
+ <span class="tag" style="color:${i(o.presence)}">${s(o.presence==="primary"?"recommended":o.presence)}</span>
91
+ ${o.recommendedAgainst?`<span class="tag" style="color:${r.critical}">Recommended against</span>`:""}
92
+ ${o.framing?`<span class="muted">${s(o.framing)}</span>`:""}
93
+ <strong>${s(o.prompt)}</strong></div>
94
+ <p>${s(o.answerExcerpt)}</p>
95
+ ${o.competitors.length?`<p><span class="label">Also recommended:</span> ${o.competitors.map(s).join(", ")}</p>`:""}</div>`).join("")}`}function Z(t){let e=new Date(t.createdAt).toLocaleString("en-CA",{dateStyle:"long",timeStyle:"short"}),a=t.type==="domain"?[`${t.pagesCrawled} pages crawled`]:t.type==="codebase"?[t.framework,`${t.filesScanned} files scanned`]:t.type==="keywords"?[`${t.keywords.length} keywords`]:t.type==="ai-visibility"?[`${t.prompts.length} prompts`]:[];return[e,...a].map(s).join(" \xB7 ")}var B=`
96
+ *{box-sizing:border-box}
97
+ html{-webkit-print-color-adjust:exact;print-color-adjust:exact}
98
+ body{margin:0;background:#fff;color:#071726;font-family:"Inter",-apple-system,"Segoe UI",Helvetica,Arial,sans-serif;font-size:10pt;line-height:1.55;-webkit-font-smoothing:antialiased}
99
+ h1,h2,h3,.score,.prio{font-family:"Archivo","Inter",-apple-system,sans-serif;font-weight:600;font-stretch:118%;letter-spacing:-0.028em}
100
+ h1,h2,h3,p{margin:0}
101
+ .masthead{display:flex;justify-content:space-between;align-items:center;padding-bottom:16px;border-bottom:1px solid rgba(9,45,80,.16)}
102
+ .masthead .site{font-size:8.5pt;color:#5a7086}
103
+ .title{padding:34px 0 22px}
104
+ .eyebrow{font-family:"Archivo","Inter",sans-serif;font-size:7.5pt;font-weight:500;letter-spacing:.32em;text-transform:uppercase;color:#0b5fd0}
105
+ h1{font-size:26pt;line-height:1.08;margin-top:12px;word-break:break-word}
106
+ .meta{margin-top:10px;color:#45607a;font-size:9pt}
107
+ .trace{height:2px;margin-bottom:26px;background:linear-gradient(90deg,#1B6BFF 0%,#3FA6FF 18%,#7DF0FF 32%,rgba(125,240,255,0) 72%)}
108
+ h2{font-size:14pt;line-height:1.15;margin:30px 0 12px;padding-top:14px;border-top:1px solid rgba(9,45,80,.16);break-after:avoid}
109
+ h3{font-size:10.5pt;margin:18px 0 8px;break-after:avoid}
110
+ .hero{display:flex;gap:34px;align-items:center;padding:22px 24px;background:#edf3fc;border:1px solid rgba(9,45,80,.16);border-radius:2px;break-inside:avoid}
111
+ .hero-score{flex:none;min-width:120px}
112
+ .score{font-size:44pt;line-height:1}
113
+ .score small{font-family:"Inter",sans-serif;font-size:11pt;font-weight:400;letter-spacing:0;color:#5a7086;margin-left:2px}
114
+ .score-label{margin-top:8px;font-size:7.5pt;letter-spacing:.22em;text-transform:uppercase;color:#45607a}
115
+ .hero-side{flex:1;min-width:0}
116
+ .cat{margin-bottom:9px;break-inside:avoid}
117
+ .cat-row{display:flex;justify-content:space-between;font-size:9pt;margin-bottom:3px}
118
+ .bar{height:5px;background:rgba(9,45,80,.1);border-radius:99px;overflow:hidden}
119
+ .bar div{height:100%;border-radius:99px}
120
+ .summary{font-size:10.5pt;line-height:1.65;max-width:72ch}
121
+ .callout{padding:14px 16px;background:#edf3fc;border:1px solid rgba(9,45,80,.16);border-left:2px solid #0b5fd0;border-radius:2px;font-size:10.5pt}
122
+ .card{border:1px solid rgba(9,45,80,.16);border-left:2px solid #0b5fd0;border-radius:2px;padding:11px 14px;margin-bottom:8px;break-inside:avoid}
123
+ .card p{margin-top:6px;font-size:9pt;color:#45607a}
124
+ .card .fix{color:#071726}
125
+ .head{display:flex;gap:8px;align-items:baseline;flex-wrap:wrap;font-size:10pt}
126
+ .prio{color:#0b5fd0;font-size:12pt;min-width:16px}
127
+ .tag{display:inline-block;font-size:6.5pt;font-weight:600;letter-spacing:.14em;text-transform:uppercase;padding:1px 6px;border:1px solid currentColor;border-radius:2px;line-height:1.5}
128
+ .label{font-weight:600;color:#071726}
129
+ .muted{color:#5a7086;font-size:8.5pt}
130
+ .note{margin-top:3px;font-size:8pt;color:#5a7086}
131
+ .list{margin:8px 0 0;padding-left:18px;font-size:9pt;color:#45607a}
132
+ .list li{margin-bottom:3px}
133
+ .counts{display:flex;flex-wrap:wrap;gap:6px 18px;font-size:9pt;color:#45607a;margin-bottom:12px}
134
+ .counts b{font-family:"Archivo","Inter",sans-serif;font-weight:600;font-size:11pt}
135
+ .evidence{margin:6px 0 0;padding-left:16px;font-family:ui-monospace,Menlo,Consolas,monospace;font-size:7.5pt;color:#5a7086;word-break:break-all}
136
+ table{width:100%;border-collapse:collapse;font-size:8.5pt}
137
+ thead{display:table-header-group}
138
+ th{text-align:left;white-space:nowrap;font-weight:600;font-size:6.5pt;letter-spacing:.14em;text-transform:uppercase;color:#45607a;border-bottom:1px solid rgba(9,45,80,.3);padding:6px 8px}
139
+ td{border-bottom:1px solid rgba(9,45,80,.16);padding:6px 8px;vertical-align:top}
140
+ tr{break-inside:avoid}
141
+ .num{text-align:right;white-space:nowrap}
142
+ .url{word-break:break-all;font-family:ui-monospace,Menlo,Consolas,monospace;font-size:7.5pt}
143
+ .missing{color:#b42318}
144
+ .raw{font-size:8pt;white-space:pre-wrap;word-break:break-all}
145
+ .closing{margin-top:40px;padding-top:16px;border-top:1px solid rgba(9,45,80,.16);display:flex;justify-content:space-between;align-items:center;gap:20px;font-size:8pt;color:#5a7086;break-inside:avoid}
146
+ @media screen{body{background:#f1f5fa;padding:40px 16px}.page{max-width:860px;margin:0 auto;background:#fff;padding:48px;border:1px solid rgba(9,45,80,.16)}}
147
+ `;function C(t){let e=d(t),a=t.type==="domain"?D(t):t.type==="codebase"?F(t):t.type==="keywords"?E(t):t.type==="content-brief"?O(t):t.type==="ai-visibility"?P(t):`<pre class="raw">${s(JSON.stringify(t,null,2))}</pre>`;return`<!doctype html>
148
+ <html lang="en-CA"><head><meta charset="utf-8">
149
+ <meta name="viewport" content="width=device-width, initial-scale=1">
150
+ <meta name="robots" content="noindex">
151
+ <title>${s(H[t.type])}: ${s(e)} | Pulse Media Agency</title>
152
+ <style>${M()}${B}</style></head><body><div class="page">
153
+ <header class="masthead">${V(20)}<span class="site">pulsemediaagency.ca</span></header>
154
+ <section class="title">
155
+ <div class="eyebrow">SEO and AEO report \xB7 ${s(H[t.type])}</div>
156
+ <h1>${s(e)}</h1>
157
+ <p class="meta">${Z(t)}</p>
158
+ </section>
159
+ <div class="trace"></div>
160
+ ${a}
161
+ <footer class="closing">${V(14)}<span>Prepared by Pulse Media Agency \xB7 pulsemediaagency.ca</span></footer>
162
+ </div></body></html>`}function J(t){let e=d(t).toLowerCase().replace(/^https?:\/\//,"").replace(/[^a-z0-9]+/g,"-").replace(/^-|-$/g,"").slice(0,60)||"report";return`pulse-${t.type}-${e}-${t.createdAt.slice(0,10)}.pdf`}async function tt(t){let e=await u("PDF export");try{let a=await e.newPage();return await a.setContent(C(t),{waitUntil:"load"}),await a.evaluate("document.fonts.ready.then(() => true)"),await a.pdf({format:"Letter",printBackground:!0,tagged:!0,outline:!0,margin:{top:"0.6in",right:"0.6in",bottom:"0.75in",left:"0.6in"},displayHeaderFooter:!0,headerTemplate:"<span></span>",footerTemplate:`<div style="width:100%;padding:0 0.6in;display:flex;justify-content:space-between;font-family:-apple-system,Helvetica,Arial,sans-serif;font-size:7pt;color:#5a7086">
163
+ <span>Pulse Media Agency \xB7 ${s(d(t))}</span>
164
+ <span><span class="pageNumber"></span> of <span class="totalPages"></span></span>
165
+ </div>`})}finally{await e.close().catch(()=>{})}}export{J as a,tt as b};
@@ -0,0 +1,165 @@
1
+ #!/usr/bin/env node
2
+ import{a as u}from"./chunk-O7EAZALL.js";var $=[-40,-160.166,1952.1763848396504,230.166],b="M0.00 -44.00 L67.50 -44.00 L78.00 -62.92 L88.50 -44.00 L135.00 -44.00 L144.00 -111.17 L156.00 23.17 L165.00 -44.00 L211.50 -44.00 L222.00 -62.92 L232.50 -44.00 L300.00 -44.00",Q="M7.49 0.00V-68.71H45.26Q52.26 -68.71 57.21 -65.87Q62.16 -63.04 64.83 -57.97Q67.49 -52.91 67.49 -46.06Q67.49 -39.14 64.75 -34.00Q62.02 -28.87 56.98 -26.04Q51.93 -23.20 44.94 -23.20H25.44V0.00ZM25.44 -37.02H40.74Q44.92 -37.02 47.13 -39.35Q49.33 -41.67 49.33 -45.95Q49.33 -48.85 48.38 -50.79Q47.42 -52.73 45.53 -53.75Q43.64 -54.78 40.74 -54.78H25.44ZM108.44 1.20Q98.47 1.20 91.34 -1.89Q84.22 -4.98 80.41 -11.20Q76.61 -17.42 76.61 -26.65V-68.71H94.55V-27.00Q94.55 -20.40 98.08 -16.59Q101.60 -12.79 108.39 -12.79Q115.17 -12.79 118.77 -16.59Q122.37 -20.40 122.37 -27.00V-68.71H140.31V-26.65Q140.31 -17.42 136.52 -11.20Q132.72 -4.98 125.59 -1.89Q118.47 1.20 108.44 1.20ZM154.59 0.00V-68.71H172.54V-14.72H207.43V0.00ZM244.87 1.20Q238.41 1.20 232.69 0.06Q226.98 -1.08 222.62 -3.68Q218.27 -6.27 215.78 -10.55Q213.29 -14.83 213.29 -21.04Q213.29 -21.42 213.31 -21.89Q213.34 -22.35 213.39 -22.71H230.73Q230.68 -22.40 230.65 -21.96Q230.63 -21.52 230.63 -21.17Q230.63 -18.03 232.26 -16.09Q233.89 -14.15 236.92 -13.28Q239.95 -12.41 243.92 -12.41Q246.08 -12.41 247.98 -12.62Q249.88 -12.83 251.40 -13.31Q252.92 -13.78 254.04 -14.54Q255.16 -15.29 255.74 -16.34Q256.32 -17.40 256.32 -18.79Q256.32 -21.15 254.57 -22.61Q252.82 -24.08 249.86 -25.10Q246.89 -26.13 243.17 -26.99Q239.46 -27.85 235.49 -28.87Q231.52 -29.89 227.81 -31.41Q224.09 -32.93 221.13 -35.22Q218.16 -37.50 216.41 -40.96Q214.67 -44.42 214.67 -49.28Q214.67 -54.73 216.96 -58.66Q219.25 -62.58 223.33 -65.07Q227.41 -67.55 232.71 -68.73Q238.00 -69.91 244.01 -69.91Q249.89 -69.91 255.07 -68.70Q260.25 -67.50 264.22 -64.97Q268.19 -62.44 270.45 -58.55Q272.72 -54.67 272.77 -49.30V-48.10H255.63V-48.78Q255.63 -51.08 254.34 -52.86Q253.05 -54.64 250.56 -55.69Q248.06 -56.73 244.40 -56.73Q240.72 -56.73 238.14 -56.02Q235.57 -55.31 234.25 -53.98Q232.93 -52.66 232.93 -50.91Q232.93 -48.74 234.67 -47.31Q236.42 -45.88 239.39 -44.90Q242.36 -43.92 246.07 -43.08Q249.78 -42.24 253.75 -41.25Q257.72 -40.27 261.44 -38.82Q265.15 -37.38 268.12 -35.13Q271.09 -32.88 272.83 -29.58Q274.58 -26.28 274.58 -21.57Q274.58 -13.43 270.72 -8.41Q266.86 -3.38 260.15 -1.09Q253.43 1.20 244.87 1.20ZM285.75 0.00V-68.71H342.52V-54.67H303.70V-41.80H337.57V-28.14H303.70V-14.03H343.17V0.00Z",y="M8.14 0.00V-68.60H25.57L38.92 -27.04Q39.52 -25.17 40.14 -22.86Q40.75 -20.55 41.30 -18.46Q41.85 -16.37 42.15 -14.84H42.88Q43.15 -16.31 43.61 -18.39Q44.07 -20.47 44.63 -22.78Q45.19 -25.08 45.80 -27.12L59.15 -68.60H76.44V0.00H65.28V-37.59Q65.28 -41.42 65.37 -45.51Q65.46 -49.59 65.59 -52.77Q65.71 -55.95 65.71 -56.94H64.94Q64.66 -55.80 63.92 -53.14Q63.18 -50.48 62.37 -47.49Q61.55 -44.50 60.88 -42.27L47.05 0.00H37.07L23.24 -42.19Q22.64 -44.12 21.94 -46.69Q21.24 -49.27 20.53 -52.00Q19.83 -54.74 19.26 -56.94H18.48Q18.58 -54.46 18.66 -50.99Q18.73 -47.52 18.82 -43.97Q18.91 -40.43 18.91 -37.59V0.00ZM102.74 0.00V-68.60H155.87V-59.03H113.89V-39.87H151.47V-30.30H113.89V-9.57H156.47V0.00ZM180.14 0.00V-68.60H206.64Q217.23 -68.60 224.73 -64.95Q232.23 -61.30 236.25 -53.75Q240.28 -46.20 240.28 -34.30Q240.28 -22.53 236.25 -14.93Q232.23 -7.33 224.73 -3.66Q217.23 0.00 206.64 0.00ZM191.29 -9.57H206.54Q211.80 -9.57 215.93 -10.98Q220.05 -12.39 222.91 -15.28Q225.76 -18.16 227.25 -22.63Q228.75 -27.09 228.75 -33.18V-35.25Q228.75 -41.48 227.25 -45.96Q225.76 -50.44 222.92 -53.32Q220.08 -56.21 215.95 -57.62Q211.83 -59.03 206.54 -59.03H191.29ZM263.24 0.00V-68.60H274.39V0.00ZM293.12 0.00 320.04 -68.60H334.35L361.34 0.00H349.26L343.15 -15.86H310.68L304.60 0.00ZM314.27 -25.36H339.49L331.84 -45.49Q331.46 -46.44 330.90 -48.00Q330.34 -49.56 329.69 -51.48Q329.04 -53.39 328.38 -55.36Q327.72 -57.33 327.17 -58.93H326.59Q325.97 -56.95 325.08 -54.35Q324.20 -51.75 323.35 -49.37Q322.50 -46.98 321.93 -45.49ZM403.12 0.00 430.04 -68.60H444.35L471.34 0.00H459.26L453.15 -15.86H420.68L414.60 0.00ZM424.27 -25.36H449.49L441.84 -45.49Q441.46 -46.44 440.90 -48.00Q440.34 -49.56 439.69 -51.48Q439.04 -53.39 438.38 -55.36Q437.72 -57.33 437.17 -58.93H436.59Q435.97 -56.95 435.08 -54.35Q434.20 -51.75 433.35 -49.37Q432.50 -46.98 431.93 -45.49ZM521.16 1.20Q504.90 1.20 496.17 -7.42Q487.45 -16.05 487.45 -34.30Q487.45 -46.25 491.52 -54.12Q495.60 -61.98 503.41 -65.89Q511.23 -69.80 522.37 -69.80Q529.14 -69.80 534.89 -68.29Q540.65 -66.78 544.95 -63.73Q549.25 -60.68 551.62 -56.08Q553.99 -51.48 553.99 -45.20H542.56Q542.56 -49.08 541.01 -51.94Q539.46 -54.79 536.72 -56.68Q533.97 -58.56 530.36 -59.43Q526.74 -60.31 522.63 -60.31Q516.88 -60.31 512.45 -58.82Q508.02 -57.34 505.02 -54.24Q502.01 -51.14 500.49 -46.44Q498.97 -41.74 498.97 -35.30V-33.20Q498.97 -24.44 501.55 -18.93Q504.13 -13.41 509.18 -10.85Q514.24 -8.29 521.52 -8.29Q528.37 -8.29 533.10 -10.29Q537.83 -12.28 540.33 -16.24Q542.84 -20.20 542.84 -26.08V-27.07H519.72V-36.41H554.01V0.00H545.85L544.80 -8.16Q541.97 -4.92 538.40 -2.84Q534.83 -0.77 530.55 0.21Q526.27 1.20 521.16 1.20ZM579.64 0.00V-68.60H632.77V-59.03H590.79V-39.87H628.37V-30.30H590.79V-9.57H633.37V0.00ZM657.04 0.00V-68.60H667.76L699.15 -25.62Q699.67 -24.99 700.42 -23.88Q701.17 -22.77 701.87 -21.68Q702.56 -20.58 702.91 -19.86H703.39Q703.39 -21.35 703.39 -22.78Q703.39 -24.20 703.39 -25.62V-68.60H714.16V0.00H703.81L672.12 -43.55Q671.34 -44.68 670.14 -46.56Q668.93 -48.44 668.28 -49.43H667.81Q667.81 -47.99 667.81 -46.53Q667.81 -45.07 667.81 -43.55V0.00ZM770.15 1.20Q759.65 1.20 752.27 -2.57Q744.89 -6.35 741.02 -14.21Q737.14 -22.07 737.14 -34.30Q737.14 -52.28 745.87 -61.04Q754.59 -69.80 770.20 -69.80Q778.84 -69.80 785.67 -66.92Q792.49 -64.05 796.46 -58.26Q800.42 -52.47 800.42 -43.59H789.05Q789.05 -49.13 786.70 -52.83Q784.36 -56.54 780.13 -58.42Q775.91 -60.31 770.30 -60.31Q763.28 -60.31 758.42 -57.57Q753.55 -54.84 751.11 -49.30Q748.67 -43.76 748.67 -35.25V-33.08Q748.67 -24.62 751.11 -19.13Q753.55 -13.64 758.38 -10.96Q763.21 -8.29 770.32 -8.29Q776.13 -8.29 780.43 -10.14Q784.74 -11.99 787.09 -15.69Q789.45 -19.40 789.45 -24.93H800.42Q800.42 -16.01 796.45 -10.22Q792.47 -4.43 785.64 -1.61Q778.80 1.20 770.15 1.20ZM842.52 0.00V-28.19L815.80 -68.60H828.61L848.22 -38.19H848.69L868.13 -68.60H880.36L853.69 -28.19V0.00Z";import x from"fs";import p from"path";import{fileURLToPath as j}from"url";var m=p.dirname(j(import.meta.url)),z=[p.join(m,"ui"),p.join(m,"../../dist/ui"),p.join(m,"../../ui/public")];function v(t){for(let e of z){let a=p.join(e,t);if(x.existsSync(a))return x.readFileSync(a)}return null}function s(t){return String(t??"").replace(/[&<>"']/g,e=>({"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#39;"})[e])}var r={critical:"#b42318",warning:"#b54708",info:"#0b5fd0",pass:"#067647"},w={critical:0,warning:1,info:2,pass:3},I=8;function f(t){return t>=80?r.pass:t>=55?r.warning:r.critical}function d(t){switch(t.type){case"codebase":return t.path;case"content-brief":return`${t.keyword} \xB7 ${t.domain}`;default:return t.domain}}var H={domain:"Domain audit",codebase:"Codebase audit",keywords:"Keyword analysis","ai-visibility":"AI visibility","content-brief":"Content brief"};function V(t){let[e,a,n,i]=$;return`<svg width="${Math.round(t*n/i)}" height="${t}" viewBox="${e} ${a} ${n} ${i}" role="img" aria-label="Pulse Media Agency">
3
+ <defs>
4
+ <linearGradient id="pm-trace" gradientUnits="userSpaceOnUse" x1="0" y1="0" x2="300" y2="0"><stop offset="0" stop-color="#0A46C8" stop-opacity="0"/><stop offset=".12" stop-color="#0A46C8" stop-opacity=".75"/><stop offset=".50" stop-color="#0E86E8"/><stop offset=".88" stop-color="#0A46C8" stop-opacity=".75"/><stop offset="1" stop-color="#0A46C8" stop-opacity="0"/></linearGradient>
5
+ <linearGradient id="pm-divider" gradientUnits="objectBoundingBox" x1="0" y1="0" x2="0" y2="1"><stop offset="0" stop-color="#7DF0FF"/><stop offset="1" stop-color="#1B6BFF"/></linearGradient>
6
+ </defs>
7
+ <path d="${b}" fill="none" stroke="url(#pm-trace)" stroke-width="8.28" stroke-linecap="round" stroke-linejoin="round"/>
8
+ <g fill="#0B1524" transform="translate(362 0)"><path d="${Q}" transform="scale(1.45773)"/></g>
9
+ <rect x="915.3" y="-102" width="5.5" height="110" rx="2.7" fill="url(#pm-divider)"/>
10
+ <g fill="#45607a" transform="translate(972.8 0)"><path d="${y}" transform="scale(1.02041)"/></g>
11
+ </svg>`}var c=null;function M(){if(c!==null)return c;let t=(e,a,n="")=>{let i=v(`fonts/${a}`);return i?`@font-face{font-family:"${e}";src:url(data:font/ttf;base64,${i.toString("base64")}) format("truetype");font-weight:100 900;${n}}`:""};return c=t("Archivo","Archivo.ttf","font-stretch:62% 125%;")+t("Inter","Inter.ttf"),c}function A(t,e,a){return`
12
+ <div class="hero">
13
+ <div class="hero-score">
14
+ <div class="score" style="color:${f(t)}">${t}<small>/100</small></div>
15
+ <div class="score-label">${s(e)}</div>
16
+ </div>
17
+ <div class="hero-side">${a}</div>
18
+ </div>`}function R(t){return t.map(e=>`
19
+ <div class="cat">
20
+ <div class="cat-row"><span>${s(e.label)}</span><strong style="color:${f(e.score)}">${e.score}</strong></div>
21
+ <div class="bar"><div style="width:${e.score}%;background:${f(e.score)}"></div></div>
22
+ ${e.note?`<p class="note">${s(e.note)}</p>`:""}
23
+ </div>`).join("")}function L(t){return A(t.overall,"Overall score",R(t.categories))}function S(t){if(!t)return"";let e=t.strengths.length?`<h3>What is already working</h3><ul class="list">${t.strengths.map(n=>`<li>${s(n)}</li>`).join("")}</ul>`:"",a=t.actionPlan.map(n=>`
24
+ <div class="card">
25
+ <div class="head"><span class="prio">${n.priority}</span><strong>${s(n.title)}</strong>
26
+ <span class="muted">Impact ${s(n.impact)} \xB7 Effort ${s(n.effort)}</span></div>
27
+ <p>${s(n.detail)}</p>
28
+ ${n.unblocks?`<p><span class="label">Unblocks:</span> ${s(n.unblocks)}</p>`:""}
29
+ ${n.leadingIndicator?`<p><span class="label">Watch:</span> ${s(n.leadingIndicator)}</p>`:""}
30
+ ${n.failureSignal?`<p><span class="label">Not working if:</span> ${s(n.failureSignal)}</p>`:""}
31
+ </div>`).join("");return`
32
+ <h2>Summary</h2>
33
+ <p class="summary">${s(t.summary)}</p>
34
+ ${e}
35
+ ${a?`<h2>Action plan</h2>${a}`:""}`}function k(t){let e=[...t].sort((i,l)=>(w[i.severity]??9)-(w[l.severity]??9)),a=i=>t.filter(l=>l.severity===i).length,n=e.map(i=>{let l=r[i.severity]??r.info,o=i.evidence.slice(0,I),h=i.evidence.length-o.length;return`
36
+ <div class="card" style="border-left-color:${l}">
37
+ <div class="head">
38
+ <span class="tag" style="color:${l}">${s(i.severity)}</span>
39
+ ${i.source==="ai"?'<span class="tag" style="color:#0846a2">AI</span>':""}
40
+ ${i.disputed?'<span class="tag" style="color:#5a7086">Disputed</span>':""}
41
+ <strong>${s(i.title)}</strong>
42
+ ${i.occurrences>1?`<span class="muted">${i.occurrences} occurrences</span>`:""}
43
+ </div>
44
+ <p>${s(i.description)}</p>
45
+ <p class="fix"><span class="label">Fix:</span> ${s(i.recommendation)}</p>
46
+ ${o.length?`<ul class="evidence">${o.map(g=>`<li>${s(g.location)}${g.snippet?`: ${s(g.snippet)}`:""}</li>`).join("")}${h>0?`<li>and ${h} more</li>`:""}</ul>`:""}
47
+ </div>`}).join("");return`
48
+ <h2>Findings</h2>
49
+ <div class="counts"><span><b style="color:${r.critical}">${a("critical")}</b> critical</span><span><b style="color:${r.warning}">${a("warning")}</b> warnings</span><span><b style="color:${r.info}">${a("info")}</b> suggestions</span></div>
50
+ ${n}`}function D(t){return`
51
+ ${L(t.scorecard)}
52
+ ${S(t.llmReport)}
53
+ ${k(t.findings)}
54
+ <h2>Crawled pages</h2>
55
+ <table><thead><tr><th>URL</th><th>Status</th><th>Title</th><th class="num">Words</th></tr></thead><tbody>
56
+ ${t.pages.map(e=>`<tr><td class="url">${s(e.url)}</td><td style="color:${e.status>=400?r.critical:"inherit"}">${e.status}</td><td>${e.title?s(e.title):'<span class="missing">Missing</span>'}</td><td class="num">${e.wordCount}</td></tr>`).join("")}
57
+ </tbody></table>`}function F(t){return`
58
+ ${L(t.scorecard)}
59
+ ${S(t.llmReport)}
60
+ ${k(t.findings)}`}function E(t){let e={strong:r.pass,partial:r.warning,missing:r.critical},a=t.keywords.map(n=>{let i=[n.notes];return n.serp&&n.serp.mismatch!=="none"&&i.push(`Search results favour ${n.serp.consensusType}; the site offers ${n.serp.sitePageType} (${n.serp.mismatch} mismatch). ${n.serp.advice}`),n.fanOut?.length&&i.push(`Related AI queries covered: ${n.fanOut.filter(l=>l.covered).length} of ${n.fanOut.length}.`),`<tr>
61
+ <td><strong>${s(n.keyword)}</strong>${i.filter(Boolean).map(l=>`<p class="note">${s(l)}</p>`).join("")}</td>
62
+ <td>${s(n.intent)}${n.stage?`<p class="note">${s(n.stage)}</p>`:""}</td>
63
+ <td><span class="tag" style="color:${e[n.coverage]}">${s(n.coverage)}</span>${n.aeoCitable?'<p class="note">AI citable</p>':""}</td>
64
+ <td class="num">${n.searchVolume!==void 0?n.searchVolume.toLocaleString("en-CA"):"\xB7"}</td>
65
+ <td class="url">${n.bestPage?s(n.bestPage):'<span class="missing">None</span>'}</td>
66
+ </tr>`}).join("");return`
67
+ <h2>Summary</h2>
68
+ <p class="summary">${s(t.summary)}</p>
69
+ <h2>Keywords</h2>
70
+ <table><thead><tr><th>Keyword</th><th>Intent</th><th>Coverage</th><th class="num">Volume</th><th>Best page</th></tr></thead><tbody>${a}</tbody></table>
71
+ ${t.gaps.length?`<h2>Content gaps</h2>${t.gaps.map(n=>`<div class="card"><div class="head"><strong>${s(n.keyword)}</strong></div><p>${s(n.rationale)}</p></div>`).join("")}`:""}`}function O(t){return`
72
+ <div class="callout"><span class="label">Information gain.</span> Why this page deserves to exist: ${s(t.informationGain)}</div>
73
+ <p class="muted" style="margin-top:12px">Format: ${s(t.format)}${t.competitorName?` \xB7 vs ${s(t.competitorName)}`:""} \xB7 Intent: ${s(t.searchIntent)} \xB7 Page type: ${s(t.recommendedPageType)}</p>
74
+ <h2>Title and meta description</h2>
75
+ <p><strong>${s(t.titleSuggestion)}</strong> <span class="muted">(${t.titleSuggestion.length} characters)</span></p>
76
+ <p style="margin-top:6px">${s(t.metaDescription)} <span class="muted">(${t.metaDescription.length} characters)</span></p>
77
+ <h2>Outline</h2>
78
+ ${t.outline.map(e=>`<div class="card"><div class="head"><strong>${s(e.heading)}</strong><span class="muted">about ${e.targetWords} words</span></div>
79
+ <ul class="list">${e.keyPoints.map(a=>`<li>${s(a)}</li>`).join("")}</ul></div>`).join("")}
80
+ ${t.competitors.length?`<h2>Ranking competitors</h2><table><thead><tr><th>Domain</th><th class="num">Depth</th><th class="num">Format</th><th class="num">SEO</th><th class="num">UX</th><th>Beat them by</th></tr></thead><tbody>
81
+ ${t.competitors.map(e=>`<tr><td class="url">${s(e.domain)}</td><td class="num">${e.scores.depth}</td><td class="num">${e.scores.formatting}</td><td class="num">${e.scores.seo}</td><td class="num">${e.scores.ux}</td><td>${s(e.weakness)}</td></tr>`).join("")}</tbody></table>`:""}
82
+ ${t.faq.length?`<h2>FAQ</h2>${t.faq.map(e=>`<div class="card"><div class="head"><strong>${s(e.question)}</strong></div><p>${s(e.answerAngle)}</p></div>`).join("")}`:""}
83
+ ${t.internalLinks.length?`<h2>Internal links</h2><ul class="evidence">${t.internalLinks.map(e=>`<li>${s(e)}</li>`).join("")}</ul>`:""}`}function P(t){let e=t.prompts.filter(o=>o.presence==="recommended"||o.presence==="primary").length,a=t.prompts.filter(o=>o.presence==="mentioned").length,n=t.prompts.filter(o=>o.recommendedAgainst).length,i=o=>o==="absent"?r.critical:o==="mentioned"?r.warning:r.pass,l=`
84
+ <div class="counts"><span><b style="color:${r.pass}">${e}</b> recommended</span><span><b style="color:${r.warning}">${a}</b> mentioned</span><span><b style="color:${r.critical}">${t.prompts.length-e-a}</b> absent</span>${n?`<span><b style="color:${r.critical}">${n}</b> recommended against</span>`:""}</div>
85
+ <p class="summary" style="font-size:10pt">${s(t.summary)}</p>`;return`
86
+ ${A(t.visibilityScore,"Visibility score",l)}
87
+ ${t.engineReadiness?.length?`<h2>Readiness by engine</h2>${R(t.engineReadiness.map(o=>({label:o.engine,score:o.score,note:o.notes.length?`Held back by: ${o.notes.join(" \xB7 ")}`:void 0})))}`:""}
88
+ <h2>Prompt results</h2>
89
+ ${t.prompts.map(o=>`<div class="card" style="border-left-color:${i(o.presence)}"><div class="head">
90
+ <span class="tag" style="color:${i(o.presence)}">${s(o.presence==="primary"?"recommended":o.presence)}</span>
91
+ ${o.recommendedAgainst?`<span class="tag" style="color:${r.critical}">Recommended against</span>`:""}
92
+ ${o.framing?`<span class="muted">${s(o.framing)}</span>`:""}
93
+ <strong>${s(o.prompt)}</strong></div>
94
+ <p>${s(o.answerExcerpt)}</p>
95
+ ${o.competitors.length?`<p><span class="label">Also recommended:</span> ${o.competitors.map(s).join(", ")}</p>`:""}</div>`).join("")}`}function Z(t){let e=new Date(t.createdAt).toLocaleString("en-CA",{dateStyle:"long",timeStyle:"short"}),a=t.type==="domain"?[`${t.pagesCrawled} pages crawled`]:t.type==="codebase"?[t.framework,`${t.filesScanned} files scanned`]:t.type==="keywords"?[`${t.keywords.length} keywords`]:t.type==="ai-visibility"?[`${t.prompts.length} prompts`]:[];return[e,...a].map(s).join(" \xB7 ")}var B=`
96
+ *{box-sizing:border-box}
97
+ html{-webkit-print-color-adjust:exact;print-color-adjust:exact}
98
+ body{margin:0;background:#fff;color:#071726;font-family:"Inter",-apple-system,"Segoe UI",Helvetica,Arial,sans-serif;font-size:10pt;line-height:1.55;-webkit-font-smoothing:antialiased}
99
+ h1,h2,h3,.score,.prio{font-family:"Archivo","Inter",-apple-system,sans-serif;font-weight:600;font-stretch:118%;letter-spacing:-0.028em}
100
+ h1,h2,h3,p{margin:0}
101
+ .masthead{display:flex;justify-content:space-between;align-items:center;padding-bottom:16px;border-bottom:1px solid rgba(9,45,80,.16)}
102
+ .masthead .site{font-size:8.5pt;color:#5a7086}
103
+ .title{padding:34px 0 22px}
104
+ .eyebrow{font-family:"Archivo","Inter",sans-serif;font-size:7.5pt;font-weight:500;letter-spacing:.32em;text-transform:uppercase;color:#0b5fd0}
105
+ h1{font-size:26pt;line-height:1.08;margin-top:12px;word-break:break-word}
106
+ .meta{margin-top:10px;color:#45607a;font-size:9pt}
107
+ .trace{height:2px;margin-bottom:26px;background:linear-gradient(90deg,#1B6BFF 0%,#3FA6FF 18%,#7DF0FF 32%,rgba(125,240,255,0) 72%)}
108
+ h2{font-size:14pt;line-height:1.15;margin:30px 0 12px;padding-top:14px;border-top:1px solid rgba(9,45,80,.16);break-after:avoid}
109
+ h3{font-size:10.5pt;margin:18px 0 8px;break-after:avoid}
110
+ .hero{display:flex;gap:34px;align-items:center;padding:22px 24px;background:#edf3fc;border:1px solid rgba(9,45,80,.16);border-radius:2px;break-inside:avoid}
111
+ .hero-score{flex:none;min-width:120px}
112
+ .score{font-size:44pt;line-height:1}
113
+ .score small{font-family:"Inter",sans-serif;font-size:11pt;font-weight:400;letter-spacing:0;color:#5a7086;margin-left:2px}
114
+ .score-label{margin-top:8px;font-size:7.5pt;letter-spacing:.22em;text-transform:uppercase;color:#45607a}
115
+ .hero-side{flex:1;min-width:0}
116
+ .cat{margin-bottom:9px;break-inside:avoid}
117
+ .cat-row{display:flex;justify-content:space-between;font-size:9pt;margin-bottom:3px}
118
+ .bar{height:5px;background:rgba(9,45,80,.1);border-radius:99px;overflow:hidden}
119
+ .bar div{height:100%;border-radius:99px}
120
+ .summary{font-size:10.5pt;line-height:1.65;max-width:72ch}
121
+ .callout{padding:14px 16px;background:#edf3fc;border:1px solid rgba(9,45,80,.16);border-left:2px solid #0b5fd0;border-radius:2px;font-size:10.5pt}
122
+ .card{border:1px solid rgba(9,45,80,.16);border-left:2px solid #0b5fd0;border-radius:2px;padding:11px 14px;margin-bottom:8px;break-inside:avoid}
123
+ .card p{margin-top:6px;font-size:9pt;color:#45607a}
124
+ .card .fix{color:#071726}
125
+ .head{display:flex;gap:8px;align-items:baseline;flex-wrap:wrap;font-size:10pt}
126
+ .prio{color:#0b5fd0;font-size:12pt;min-width:16px}
127
+ .tag{display:inline-block;font-size:6.5pt;font-weight:600;letter-spacing:.14em;text-transform:uppercase;padding:1px 6px;border:1px solid currentColor;border-radius:2px;line-height:1.5}
128
+ .label{font-weight:600;color:#071726}
129
+ .muted{color:#5a7086;font-size:8.5pt}
130
+ .note{margin-top:3px;font-size:8pt;color:#5a7086}
131
+ .list{margin:8px 0 0;padding-left:18px;font-size:9pt;color:#45607a}
132
+ .list li{margin-bottom:3px}
133
+ .counts{display:flex;flex-wrap:wrap;gap:6px 18px;font-size:9pt;color:#45607a;margin-bottom:12px}
134
+ .counts b{font-family:"Archivo","Inter",sans-serif;font-weight:600;font-size:11pt}
135
+ .evidence{margin:6px 0 0;padding-left:16px;font-family:ui-monospace,Menlo,Consolas,monospace;font-size:7.5pt;color:#5a7086;word-break:break-all}
136
+ table{width:100%;border-collapse:collapse;font-size:8.5pt}
137
+ thead{display:table-header-group}
138
+ th{text-align:left;font-weight:600;font-size:6.5pt;letter-spacing:.14em;text-transform:uppercase;color:#45607a;border-bottom:1px solid rgba(9,45,80,.3);padding:6px 8px}
139
+ td{border-bottom:1px solid rgba(9,45,80,.16);padding:6px 8px;vertical-align:top}
140
+ tr{break-inside:avoid}
141
+ .num{text-align:right;white-space:nowrap}
142
+ .url{word-break:break-all;font-family:ui-monospace,Menlo,Consolas,monospace;font-size:7.5pt}
143
+ .missing{color:#b42318}
144
+ .raw{font-size:8pt;white-space:pre-wrap;word-break:break-all}
145
+ .closing{margin-top:40px;padding-top:16px;border-top:1px solid rgba(9,45,80,.16);display:flex;justify-content:space-between;align-items:center;gap:20px;font-size:8pt;color:#5a7086;break-inside:avoid}
146
+ @media screen{body{background:#f1f5fa;padding:40px 16px}.page{max-width:860px;margin:0 auto;background:#fff;padding:48px;border:1px solid rgba(9,45,80,.16)}}
147
+ `;function C(t){let e=d(t),a=t.type==="domain"?D(t):t.type==="codebase"?F(t):t.type==="keywords"?E(t):t.type==="content-brief"?O(t):t.type==="ai-visibility"?P(t):`<pre class="raw">${s(JSON.stringify(t,null,2))}</pre>`;return`<!doctype html>
148
+ <html lang="en-CA"><head><meta charset="utf-8">
149
+ <meta name="viewport" content="width=device-width, initial-scale=1">
150
+ <meta name="robots" content="noindex">
151
+ <title>${s(H[t.type])}: ${s(e)} | Pulse Media Agency</title>
152
+ <style>${M()}${B}</style></head><body><div class="page">
153
+ <header class="masthead">${V(20)}<span class="site">pulsemediaagency.ca</span></header>
154
+ <section class="title">
155
+ <div class="eyebrow">SEO and AEO report \xB7 ${s(H[t.type])}</div>
156
+ <h1>${s(e)}</h1>
157
+ <p class="meta">${Z(t)}</p>
158
+ </section>
159
+ <div class="trace"></div>
160
+ ${a}
161
+ <footer class="closing">${V(14)}<span>Prepared by Pulse Media Agency \xB7 pulsemediaagency.ca</span></footer>
162
+ </div></body></html>`}function J(t){let e=d(t).toLowerCase().replace(/^https?:\/\//,"").replace(/[^a-z0-9]+/g,"-").replace(/^-|-$/g,"").slice(0,60)||"report";return`pulse-${t.type}-${e}-${t.createdAt.slice(0,10)}.pdf`}async function tt(t){let e=await u("PDF export");try{let a=await e.newPage();return await a.setContent(C(t),{waitUntil:"load"}),await a.evaluate("document.fonts.ready.then(() => true)"),await a.pdf({format:"Letter",printBackground:!0,tagged:!0,outline:!0,margin:{top:"0.6in",right:"0.6in",bottom:"0.75in",left:"0.6in"},displayHeaderFooter:!0,headerTemplate:"<span></span>",footerTemplate:`<div style="width:100%;padding:0 0.6in;display:flex;justify-content:space-between;font-family:-apple-system,Helvetica,Arial,sans-serif;font-size:7pt;color:#5a7086">
163
+ <span>Pulse Media Agency \xB7 ${s(d(t))}</span>
164
+ <span><span class="pageNumber"></span> of <span class="totalPages"></span></span>
165
+ </div>`})}finally{await e.close().catch(()=>{})}}export{J as a,tt as b};
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ import*as S from"cheerio";var _="PulseMediaSEOAudit/0.1 (+https://pulsemediaagency.ca)";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 U(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 M=d.text().replace(/\s+/g," ").trim();M&&(c+=M.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=U(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(...U(p.text,t))}}h=o.filter(e=>e.startsWith(t))}let x=null;if(n.render){let{createRenderer:a}=await import("./render-KNG7JE36.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,19 @@
1
+ #!/usr/bin/env node
2
+ import{a as T,i as s}from"./chunk-AIOAF3VX.js";import R from"fs";import y from"path";import A from"better-sqlite3";import n from"fs";import d from"path";import g from"os";var a=d.join(g.homedir(),".pulsemedia"),i=d.join(a,"config.json"),u=d.join(a,"reports"),m=d.join(g.homedir(),".eternify");function O(){if(!(n.existsSync(a)||!n.existsSync(m)))try{n.cpSync(m,a,{recursive:!0}),n.existsSync(i)&&n.chmodSync(i,384)}catch{}}O();function l(){n.mkdirSync(u,{recursive:!0})}function C(){try{let r=n.readFileSync(i,"utf8");return T.parse(JSON.parse(r))}catch{return null}}function D(r){l();let t=new Date().toISOString(),e={...r,createdAt:r.createdAt??t,updatedAt:t};n.writeFileSync(i,JSON.stringify(e,null,2)+`
3
+ `,{mode:384}),n.chmodSync(i,384)}function v(r){switch(r.type){case"codebase":return r.path;case"content-brief":return`${r.keyword} \u2014 ${r.domain}`;default:return r.domain}}function b(r){return"scorecard"in r?r.scorecard.overall:r.type==="ai-visibility"?r.visibilityScore:null}var f=class{db;constructor(t,e){R.mkdirSync(y.dirname(t),{recursive:!0}),this.db=new A(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(c=>c.endsWith(".json"))}catch{return}if(e.length===0)return;let o=this.db.prepare("SELECT 1 FROM reports WHERE id = ?");for(let c of e)try{let S=s.parse(JSON.parse(R.readFileSync(y.join(t,c),"utf8")));o.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,v(t),t.createdAt,b(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 s.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 o of t)try{e.push(s.parse(JSON.parse(o.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 s.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 o=this.db.prepare("SELECT value, updated_at FROM kv WHERE key = ?").get(t);if(!o||e!==void 0&&Date.now()-Date.parse(o.updated_at)>e)return null;try{return JSON.parse(o.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 p(){return E||(l(),E=new f(y.join(a,"data.db"),u)),E}var k=r=>p().saveReport(r),J=r=>/^[a-zA-Z0-9-]+$/.test(r)?p().loadReport(r):null,M=()=>p().listReports(),U=()=>p().listReportSummaries(),Y=r=>/^[a-zA-Z0-9-]+$/.test(r)?p().deleteReport(r):!1;export{i as a,C as b,D as c,f as d,p as e,k as f,J as g,M as h,U as i,Y as j};
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ async function d(t="JS rendering"){let e;try{({chromium:e}=await import("playwright-core"))}catch{throw new Error(`${t} requires playwright-core \u2014 run \`npm install playwright-core\` where the tool is installed.`)}try{return await e.launch({channel:"chrome",headless:!0})}catch{try{return await e.launch({channel:"msedge",headless:!0})}catch{throw new Error(`${t} needs Google Chrome (or Edge) installed on this machine.`)}}}async function u(){let t=await d(),e=await t.newContext({userAgent:"PulseMediaSEOAudit/0.1 (+https://pulsemediaagency.ca)"});return{async render(i,a){let r=await e.newPage();try{let n=await r.goto(i,{timeout:a,waitUntil:"domcontentloaded"});await r.waitForLoadState("networkidle",{timeout:Math.min(5e3,a)}).catch(()=>{});let c=await r.content(),o={};if(n){let s=await n.allHeaders().catch(()=>({}));for(let[l,h]of Object.entries(s))o[l.toLowerCase()]=h}return{html:c,status:n?.status()??200,finalUrl:r.url(),headers:o}}catch{return null}finally{await r.close().catch(()=>{})}},async close(){await e.close().catch(()=>{}),await t.close().catch(()=>{})}}}export{d as a,u as b};