@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 +3 -3
- package/README.md +17 -13
- package/THIRD-PARTY-NOTICES.md +9 -0
- package/dist/chunk-5J5XNNV6.js +165 -0
- package/dist/chunk-FMERCM5P.js +165 -0
- package/dist/chunk-KQB6L3WV.js +2 -0
- package/dist/chunk-MQJGARAM.js +19 -0
- package/dist/chunk-O7EAZALL.js +2 -0
- package/dist/cli.js +42 -125
- package/dist/crawler-LJDICWL6.js +2 -0
- package/dist/exportPdf-26EHUONL.js +2 -0
- package/dist/exportPdf-D7NKRT2E.js +2 -0
- package/dist/render-KNG7JE36.js +2 -0
- package/dist/store-PDEFA2OG.js +2 -0
- package/dist/ui/assets/index-CQc4R359.js +67 -0
- package/dist/ui/assets/index-C_IduXlG.css +1 -0
- package/dist/ui/favicon.svg +20 -0
- package/dist/ui/fonts/Archivo.ttf +0 -0
- package/dist/ui/fonts/Inter.ttf +0 -0
- package/dist/ui/index.html +5 -3
- package/package.json +2 -2
- package/dist/ui/assets/index-CFVxJXmH.css +0 -1
- package/dist/ui/assets/index-Cmt-9HyO.js +0 -67
package/LICENSE
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
|
|
1
|
+
Pulse Media Agency SEO/AEO Audit — End User License Agreement
|
|
2
2
|
|
|
3
|
-
Copyright (c) 2026
|
|
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
|
|
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
|
-
#
|
|
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
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
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 * * *
|
|
123
|
+
Or in cron for scheduled monitoring: `0 6 * * * pulse-seo drift yourdomain.com`
|
|
124
124
|
|
|
125
|
-
Config lives in `~/.
|
|
126
|
-
a local SQLite database at `~/.
|
|
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 (
|
|
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/)
|
|
154
|
+
Built by [Krutik Parikh](https://www.krutikparikh.ca/) for [Pulse Media Agency](https://pulsemediaagency.ca/).
|
package/THIRD-PARTY-NOTICES.md
CHANGED
|
@@ -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=>({"&":"&","<":"<",">":">",'"':""","'":"'"})[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=>({"&":"&","<":"<",">":">",'"':""","'":"'"})[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};
|