seo-intel 1.4.9 → 1.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +29 -0
- package/cli.js +16 -8
- package/extractor/qwen.js +15 -2
- package/package.json +1 -1
- package/reports/generate-html.js +27 -15
- package/server.js +239 -513
- package/setup/checks.js +44 -16
- package/setup/web-routes.js +4 -0
- package/setup/wizard.html +85 -15
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,34 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.5.1 (2026-04-11)
|
|
4
|
+
|
|
5
|
+
### Setup Wizard
|
|
6
|
+
- Fixed Playwright detection on macOS — now checks correct browser cache paths instead of legacy node_modules location
|
|
7
|
+
- Added persistent "Open Dashboard" link in wizard header, visible on all setup steps
|
|
8
|
+
- Renamed floating helper card to "Agentic Installations" with extended per-runtime setup prompts
|
|
9
|
+
- Cloud model cards now show live connection status (Connected via API key or OpenClaw gateway)
|
|
10
|
+
- OpenClaw gateway model detection with authenticated `/v1/models` query
|
|
11
|
+
|
|
12
|
+
### Extraction: LAN host model fix
|
|
13
|
+
- Fixed LAN/fallback hosts checking for wrong model (used stale `OLLAMA_FALLBACK_MODEL` instead of project-selected model)
|
|
14
|
+
- All Ollama hosts now use the project's configured extraction model consistently
|
|
15
|
+
- Added `OLLAMA_HOSTS` support — comma-separated LAN hosts from setup wizard are picked up by extractor
|
|
16
|
+
|
|
17
|
+
### Dashboard
|
|
18
|
+
- Stealth toggle moved next to Crawl button (only affects crawl, not extract)
|
|
19
|
+
- Analysis buttons (Analyze, Brief, Keywords, Templates) get subtle blue accent border
|
|
20
|
+
- Visual separator between action and intelligence command groups
|
|
21
|
+
|
|
22
|
+
## 1.5.0 (2026-04-10)
|
|
23
|
+
|
|
24
|
+
### Export: dashboard data, not raw DB dumps
|
|
25
|
+
- **Complete rewrite** of export endpoint — now exports the same processed data the dashboard shows
|
|
26
|
+
- Dev export: technical scorecard, quick wins, technical gaps, internal link stats, watch alerts
|
|
27
|
+
- Content export: keyword gaps, long-tails, new pages, content gaps, positioning, citability issues
|
|
28
|
+
- AI Pipeline: all actionable sections combined in structured JSON
|
|
29
|
+
- ~14 KB dev export instead of ~200 KB of competitor bloat
|
|
30
|
+
- No more raw link/heading/schema/keyword dumps — every item is an action
|
|
31
|
+
|
|
3
32
|
## 1.4.9 (2026-04-10)
|
|
4
33
|
|
|
5
34
|
### Security
|
package/cli.js
CHANGED
|
@@ -69,19 +69,27 @@ function defaultSiteUrl(domain) {
|
|
|
69
69
|
function resolveExtractionRuntime(config) {
|
|
70
70
|
const primaryUrl = config?.crawl?.ollamaHost || process.env.OLLAMA_URL || 'http://localhost:11434';
|
|
71
71
|
const primaryModel = config?.crawl?.extractionModel || process.env.OLLAMA_MODEL || 'gemma4:e4b';
|
|
72
|
-
const fallbackUrl = process.env.OLLAMA_FALLBACK_URL || '';
|
|
73
|
-
const fallbackModel = process.env.OLLAMA_FALLBACK_MODEL || primaryModel;
|
|
74
72
|
const localhost = 'http://localhost:11434';
|
|
73
|
+
const norm = h => String(h || '').trim().replace(/\/+$/, '');
|
|
75
74
|
|
|
76
75
|
const candidates = [
|
|
77
|
-
{ host:
|
|
76
|
+
{ host: norm(primaryUrl), model: String(primaryModel).trim() || 'gemma4:e4b' },
|
|
78
77
|
];
|
|
79
78
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
79
|
+
// Legacy single fallback — always use project-selected model, not OLLAMA_FALLBACK_MODEL
|
|
80
|
+
const fallbackUrl = norm(process.env.OLLAMA_FALLBACK_URL || '');
|
|
81
|
+
if (fallbackUrl && !candidates.some(c => c.host === fallbackUrl)) {
|
|
82
|
+
candidates.push({ host: fallbackUrl, model: String(primaryModel).trim() || 'gemma4:e4b' });
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// OLLAMA_HOSTS — comma-separated LAN hosts from setup wizard
|
|
86
|
+
if (process.env.OLLAMA_HOSTS) {
|
|
87
|
+
for (const h of process.env.OLLAMA_HOSTS.split(',')) {
|
|
88
|
+
const host = norm(h);
|
|
89
|
+
if (host && !candidates.some(c => c.host === host)) {
|
|
90
|
+
candidates.push({ host, model: String(primaryModel).trim() || 'gemma4:e4b' });
|
|
91
|
+
}
|
|
92
|
+
}
|
|
85
93
|
}
|
|
86
94
|
|
|
87
95
|
if (!candidates.some(candidate => candidate.host === localhost)) {
|
package/extractor/qwen.js
CHANGED
|
@@ -24,16 +24,29 @@ function getConfiguredOllamaRoutes() {
|
|
|
24
24
|
const primaryUrl = normalizeHost(process.env.OLLAMA_URL || DEFAULT_OLLAMA_URL) || DEFAULT_OLLAMA_URL;
|
|
25
25
|
const primaryModel = String(process.env.OLLAMA_MODEL || DEFAULT_OLLAMA_MODEL).trim() || DEFAULT_OLLAMA_MODEL;
|
|
26
26
|
const fallbackUrl = normalizeHost(process.env.OLLAMA_FALLBACK_URL || '');
|
|
27
|
-
|
|
27
|
+
// BUG FIX: fallback hosts MUST use the project-selected model (primaryModel),
|
|
28
|
+
// not a separate OLLAMA_FALLBACK_MODEL env var. The project config sets
|
|
29
|
+
// OLLAMA_MODEL to the user's choice — all hosts should respect that.
|
|
30
|
+
const fallbackModel = primaryModel;
|
|
28
31
|
|
|
29
32
|
const candidates = [
|
|
30
33
|
{ label: 'primary', host: primaryUrl, model: primaryModel },
|
|
31
34
|
];
|
|
32
35
|
|
|
33
|
-
if (fallbackUrl) {
|
|
36
|
+
if (fallbackUrl && !candidates.some(r => r.host === normalizeHost(fallbackUrl))) {
|
|
34
37
|
candidates.push({ label: 'fallback', host: fallbackUrl, model: fallbackModel });
|
|
35
38
|
}
|
|
36
39
|
|
|
40
|
+
// Support OLLAMA_HOSTS — comma-separated list of additional LAN Ollama hosts
|
|
41
|
+
if (process.env.OLLAMA_HOSTS) {
|
|
42
|
+
for (const h of process.env.OLLAMA_HOSTS.split(',')) {
|
|
43
|
+
const host = normalizeHost(h);
|
|
44
|
+
if (host && !candidates.some(r => r.host === host)) {
|
|
45
|
+
candidates.push({ label: 'lan', host, model: primaryModel });
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
37
50
|
if (!candidates.some(route => route.host === LOCALHOST_OLLAMA_URL)) {
|
|
38
51
|
candidates.push({ label: 'localhost', host: LOCALHOST_OLLAMA_URL, model: primaryModel });
|
|
39
52
|
}
|
package/package.json
CHANGED
package/reports/generate-html.js
CHANGED
|
@@ -40,7 +40,7 @@ function cardExportHtml(section, project) {
|
|
|
40
40
|
/**
|
|
41
41
|
* Gather all dashboard data for a single project
|
|
42
42
|
*/
|
|
43
|
-
function gatherProjectData(db, project, config) {
|
|
43
|
+
export function gatherProjectData(db, project, config) {
|
|
44
44
|
const targetDomain = config.target.domain;
|
|
45
45
|
const competitorDomains = config.competitors.map(c => c.domain);
|
|
46
46
|
const allDomains = [targetDomain, ...competitorDomains];
|
|
@@ -1466,6 +1466,12 @@ function buildHtmlTemplate(data, opts = {}) {
|
|
|
1466
1466
|
.term-btn:hover { border-color: var(--accent-gold); color: var(--accent-gold); }
|
|
1467
1467
|
.term-btn:active { background: rgba(232,213,163,0.1); }
|
|
1468
1468
|
.term-btn i { margin-right: 3px; font-size: 0.55rem; }
|
|
1469
|
+
.term-btn-intel { border-color: rgba(96,165,250,0.2); }
|
|
1470
|
+
.term-btn-intel:hover { border-color: rgba(96,165,250,0.6); color: rgba(150,200,255,0.9); }
|
|
1471
|
+
.term-btn-intel:active { background: rgba(96,165,250,0.08); }
|
|
1472
|
+
.term-stealth { display:inline-flex; align-items:center; gap:4px; font-size:0.58rem; color:var(--text-muted); cursor:pointer; user-select:none; margin-left:2px; padding:2px 6px; border-radius:4px; border:1px solid transparent; transition:all 0.15s; }
|
|
1473
|
+
.term-stealth:hover { border-color: rgba(124,109,235,0.3); color: var(--text-secondary); }
|
|
1474
|
+
.term-stealth input[type="checkbox"] { accent-color: var(--accent-purple,#7c6deb); width:12px; height:12px; cursor:pointer; }
|
|
1469
1475
|
|
|
1470
1476
|
/* ─── Terminal + Export split layout ───────────────────────────────── */
|
|
1471
1477
|
.term-split {
|
|
@@ -2122,10 +2128,6 @@ function buildHtmlTemplate(data, opts = {}) {
|
|
|
2122
2128
|
<button class="es-btn es-btn-restart" id="btnRestart${suffix}" onclick="restartServer()">
|
|
2123
2129
|
<i class="fa-solid fa-rotate-right"></i> Restart
|
|
2124
2130
|
</button>
|
|
2125
|
-
<label class="es-stealth-toggle">
|
|
2126
|
-
<input type="checkbox" id="stealthToggle${suffix}"${extractionStatus.liveProgress?.stealth ? ' checked' : ''}>
|
|
2127
|
-
<i class="fa-solid fa-user-ninja"></i> Stealth
|
|
2128
|
-
</label>
|
|
2129
2131
|
</div>
|
|
2130
2132
|
</div>
|
|
2131
2133
|
</div>
|
|
@@ -2147,11 +2149,13 @@ function buildHtmlTemplate(data, opts = {}) {
|
|
|
2147
2149
|
<div style="padding:8px 12px;background:#111;border-bottom:1px solid var(--border-subtle);display:flex;flex-wrap:wrap;gap:6px;align-items:center;">
|
|
2148
2150
|
<span style="font-size:0.6rem;color:var(--text-muted);margin-right:4px;"><i class="fa-solid fa-play" style="margin-right:3px;"></i>Run:</span>
|
|
2149
2151
|
<button class="term-btn" data-cmd="crawl" data-project="${project}"><i class="fa-solid fa-spider"></i> Crawl</button>
|
|
2152
|
+
<label class="term-stealth"><input type="checkbox" id="stealthToggle${suffix}"${extractionStatus.liveProgress?.stealth ? ' checked' : ''}><i class="fa-solid fa-user-ninja"></i></label>
|
|
2150
2153
|
${pro ? `<button class="term-btn" data-cmd="extract" data-project="${project}"><i class="fa-solid fa-brain"></i> Extract</button>
|
|
2151
|
-
<
|
|
2152
|
-
<button class="term-btn" data-cmd="
|
|
2153
|
-
<button class="term-btn" data-cmd="
|
|
2154
|
-
<button class="term-btn" data-cmd="
|
|
2154
|
+
<span style="width:1px;height:16px;background:var(--border-subtle);margin:0 2px;"></span>
|
|
2155
|
+
<button class="term-btn term-btn-intel" data-cmd="analyze" data-project="${project}"><i class="fa-solid fa-chart-column"></i> Analyze</button>
|
|
2156
|
+
<button class="term-btn term-btn-intel" data-cmd="brief" data-project="${project}"><i class="fa-solid fa-file-lines"></i> Brief</button>
|
|
2157
|
+
<button class="term-btn term-btn-intel" data-cmd="keywords" data-project="${project}"><i class="fa-solid fa-key"></i> Keywords</button>
|
|
2158
|
+
<button class="term-btn term-btn-intel" data-cmd="templates" data-project="${project}"><i class="fa-solid fa-clone"></i> Templates</button>` : ''}
|
|
2155
2159
|
<button class="term-btn" data-cmd="status" data-project=""><i class="fa-solid fa-circle-info"></i> Status</button>
|
|
2156
2160
|
<button class="term-btn" data-cmd="guide" data-project="${project}"><i class="fa-solid fa-map"></i> Guide</button>
|
|
2157
2161
|
<button class="term-btn" data-cmd="setup" data-project="" style="margin-left:auto;border-color:rgba(232,213,163,0.25);"><i class="fa-solid fa-gear"></i> Setup</button>
|
|
@@ -2368,10 +2372,12 @@ function buildHtmlTemplate(data, opts = {}) {
|
|
|
2368
2372
|
const proj = btn.getAttribute('data-project');
|
|
2369
2373
|
const scope = btn.getAttribute('data-scope');
|
|
2370
2374
|
var extra = scope ? { scope: scope } : {};
|
|
2371
|
-
// Crawl
|
|
2372
|
-
if (cmd === 'crawl'
|
|
2375
|
+
// Crawl: read stealth toggle; Crawl/extract: update status bar
|
|
2376
|
+
if (cmd === 'crawl') {
|
|
2373
2377
|
var stealthEl = btn.closest('.project-panel')?.querySelector('[id^="stealthToggle"]') || document.getElementById('stealthToggle' + suffix);
|
|
2374
2378
|
if (stealthEl?.checked) extra.stealth = true;
|
|
2379
|
+
}
|
|
2380
|
+
if (cmd === 'crawl' || cmd === 'extract') {
|
|
2375
2381
|
if (window._setButtonsState) window._setButtonsState(true, cmd);
|
|
2376
2382
|
if (window._startPolling) window._startPolling();
|
|
2377
2383
|
}
|
|
@@ -4057,9 +4063,12 @@ function buildHtmlTemplate(data, opts = {}) {
|
|
|
4057
4063
|
let pollTimer = null;
|
|
4058
4064
|
|
|
4059
4065
|
window.startJob = function(command, proj) {
|
|
4060
|
-
var stealth = document.getElementById('stealthToggle' + sfx)?.checked || false;
|
|
4061
4066
|
var extra = {};
|
|
4062
|
-
|
|
4067
|
+
// Stealth only applies to crawl — extract has no network
|
|
4068
|
+
if (command === 'crawl') {
|
|
4069
|
+
var stealth = document.getElementById('stealthToggle' + sfx)?.checked || false;
|
|
4070
|
+
if (stealth) extra.stealth = true;
|
|
4071
|
+
}
|
|
4063
4072
|
|
|
4064
4073
|
// Route through terminal for visible output
|
|
4065
4074
|
if (window._terminalRun) {
|
|
@@ -5279,9 +5288,12 @@ function buildMultiHtmlTemplate(allProjectData) {
|
|
|
5279
5288
|
|
|
5280
5289
|
window.startJob = function(command, proj) {
|
|
5281
5290
|
var sfx = '-' + proj;
|
|
5282
|
-
var stealth = document.getElementById('stealthToggle' + sfx)?.checked || false;
|
|
5283
5291
|
var extra = {};
|
|
5284
|
-
|
|
5292
|
+
// Stealth only applies to crawl — extract has no network
|
|
5293
|
+
if (command === 'crawl') {
|
|
5294
|
+
var stealth = document.getElementById('stealthToggle' + sfx)?.checked || false;
|
|
5295
|
+
if (stealth) extra.stealth = true;
|
|
5296
|
+
}
|
|
5285
5297
|
|
|
5286
5298
|
// Route through terminal for visible output
|
|
5287
5299
|
if (window._terminalRun) {
|