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 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: String(primaryUrl).trim().replace(/\/+$/, ''), model: String(primaryModel).trim() || 'gemma4:e4b' },
76
+ { host: norm(primaryUrl), model: String(primaryModel).trim() || 'gemma4:e4b' },
78
77
  ];
79
78
 
80
- if (fallbackUrl) {
81
- candidates.push({
82
- host: String(fallbackUrl).trim().replace(/\/+$/, ''),
83
- model: String(fallbackModel).trim() || String(primaryModel).trim() || 'gemma4:e4b',
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
- const fallbackModel = String(process.env.OLLAMA_FALLBACK_MODEL || primaryModel).trim() || primaryModel;
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "seo-intel",
3
- "version": "1.4.9",
3
+ "version": "1.5.1",
4
4
  "description": "Local Ahrefs-style SEO competitor intelligence. Crawl → SQLite → cloud analysis.",
5
5
  "type": "module",
6
6
  "license": "SEE LICENSE IN LICENSE",
@@ -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
- <button class="term-btn" data-cmd="analyze" data-project="${project}"><i class="fa-solid fa-chart-column"></i> Analyze</button>
2152
- <button class="term-btn" data-cmd="brief" data-project="${project}"><i class="fa-solid fa-file-lines"></i> Brief</button>
2153
- <button class="term-btn" data-cmd="keywords" data-project="${project}"><i class="fa-solid fa-key"></i> Keywords</button>
2154
- <button class="term-btn" data-cmd="templates" data-project="${project}"><i class="fa-solid fa-clone"></i> Templates</button>` : ''}
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/extract: read stealth toggle + update status bar
2372
- if (cmd === 'crawl' || cmd === 'extract') {
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
- if (stealth) extra.stealth = true;
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
- if (stealth) extra.stealth = true;
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) {