seo-intel 1.2.2 → 1.2.4

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,11 +1,18 @@
1
1
  # Changelog
2
2
 
3
- ## 1.2.1 (2026-03-28)
3
+ ## 1.2.4 (2026-03-31)
4
+
5
+ ### Fixes
6
+ - Fix favicon serving — explicit `Content-Type: image/png` header prevents browser showing cached favicon from crawled sites
7
+ - Favicon link tag now cache-busts on dashboard regeneration
8
+
9
+ ## 1.2.3 (2026-03-28)
4
10
 
5
11
  ### Dashboard
6
12
  - Remove redundant Crawl/Extract buttons from status bar — terminal already has them
7
13
  - Status bar now shows only Stop, Restart, and Stealth toggle (cleaner UI)
8
- - Stealth toggle still controls `--stealth` flag for terminal Crawl/Extract buttons
14
+ - Fix stealth toggle scoping in multi-project dashboard each project panel reads its own toggle
15
+ - Stop button now clears crashed processes (dead PIDs) instead of showing stale "running" state
9
16
 
10
17
  ## 1.2.0 (2026-03-28)
11
18
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "seo-intel",
3
- "version": "1.2.2",
3
+ "version": "1.2.4",
4
4
  "description": "Local Ahrefs-style SEO competitor intelligence. Crawl → SQLite → cloud analysis.",
5
5
  "type": "module",
6
6
  "license": "SEE LICENSE IN LICENSE",
@@ -208,7 +208,7 @@ function buildHtmlTemplate(data, opts = {}) {
208
208
  <meta charset="UTF-8">
209
209
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
210
210
  <title>SEO Intel ${pro ? 'Dashboard' : 'Preview'} — ${project.toUpperCase()}</title>
211
- <link rel="icon" type="image/png" href="/favicon.png">
211
+ <link rel="icon" type="image/png" href="/favicon.png?v=${Date.now()}">
212
212
  <link rel="preconnect" href="https://fonts.googleapis.com">
213
213
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
214
214
  <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600&family=Syne:wght@600;700;800&display=swap" rel="stylesheet">
@@ -2162,7 +2162,7 @@ function buildHtmlTemplate(data, opts = {}) {
2162
2162
  var extra = scope ? { scope: scope } : {};
2163
2163
  // Crawl/extract: read stealth toggle + update status bar
2164
2164
  if (cmd === 'crawl' || cmd === 'extract') {
2165
- var stealthEl = document.querySelector('[id^="stealthToggle"]');
2165
+ var stealthEl = btn.closest('.project-panel')?.querySelector('[id^="stealthToggle"]') || document.getElementById('stealthToggle' + suffix);
2166
2166
  if (stealthEl?.checked) extra.stealth = true;
2167
2167
  if (window._setButtonsState) window._setButtonsState(true, cmd);
2168
2168
  if (window._startPolling) window._startPolling();
package/server.js CHANGED
@@ -339,32 +339,39 @@ async function handleRequest(req, res) {
339
339
  if (req.method === 'POST' && path === '/api/stop') {
340
340
  try {
341
341
  const progress = readProgress();
342
- if (!progress || progress.status !== 'running' || !progress.pid) {
342
+ if (!progress || !progress.pid) {
343
343
  json(res, 404, { error: 'No running job to stop' });
344
344
  return;
345
345
  }
346
- try {
347
- // Graceful: SIGTERM lets the CLI close browsers / write progress
348
- process.kill(progress.pid, 'SIGTERM');
349
- // Escalate: SIGKILL after 5s if still alive (stealth browser cleanup needs time)
350
- setTimeout(() => {
351
- try { process.kill(progress.pid, 0); } catch { return; } // already dead
352
- try { process.kill(progress.pid, 'SIGKILL'); } catch {}
353
- }, 5000);
354
- } catch (e) {
355
- if (e.code !== 'ESRCH') throw e;
356
- // Already dead
346
+
347
+ // Check if process is actually alive
348
+ let isAlive = false;
349
+ try { process.kill(progress.pid, 0); isAlive = true; } catch { isAlive = false; }
350
+
351
+ if (isAlive) {
352
+ try {
353
+ // Graceful: SIGTERM lets the CLI close browsers / write progress
354
+ process.kill(progress.pid, 'SIGTERM');
355
+ // Escalate: SIGKILL after 5s if still alive (stealth browser cleanup needs time)
356
+ setTimeout(() => {
357
+ try { process.kill(progress.pid, 0); } catch { return; } // already dead
358
+ try { process.kill(progress.pid, 'SIGKILL'); } catch {}
359
+ }, 5000);
360
+ } catch (e) {
361
+ if (e.code !== 'ESRCH') throw e;
362
+ }
357
363
  }
358
- // Update progress file (CLI also writes this on SIGTERM, but server does it too as safety net)
364
+
365
+ // Update progress file — clears both running and crashed states
359
366
  try {
360
367
  writeFileSync(PROGRESS_FILE, JSON.stringify({
361
368
  ...progress,
362
- status: 'stopped',
369
+ status: isAlive ? 'stopped' : 'crashed_cleared',
363
370
  stopped_at: Date.now(),
364
371
  updated_at: Date.now(),
365
372
  }, null, 2));
366
373
  } catch { /* best-effort */ }
367
- json(res, 200, { stopped: true, pid: progress.pid, command: progress.command });
374
+ json(res, 200, { stopped: true, pid: progress.pid, command: progress.command, wasAlive: isAlive });
368
375
  } catch (e) {
369
376
  json(res, 500, { error: e.message });
370
377
  }
@@ -671,7 +678,11 @@ async function handleRequest(req, res) {
671
678
  if (req.method === 'GET' && (path === '/favicon.ico' || path === '/favicon.png')) {
672
679
  const faviconPath = join(__dirname, 'seo-intel.png');
673
680
  if (existsSync(faviconPath)) {
674
- serveFile(res, faviconPath);
681
+ res.writeHead(200, {
682
+ 'Content-Type': 'image/png',
683
+ 'Cache-Control': 'public, max-age=3600',
684
+ });
685
+ res.end(readFileSync(faviconPath));
675
686
  } else {
676
687
  res.writeHead(204); res.end();
677
688
  }