seo-intel 1.2.0 → 1.2.3

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,13 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.2.3 (2026-03-28)
4
+
5
+ ### Dashboard
6
+ - Remove redundant Crawl/Extract buttons from status bar — terminal already has them
7
+ - Status bar now shows only Stop, Restart, and Stealth toggle (cleaner UI)
8
+ - Fix stealth toggle scoping in multi-project dashboard — each project panel reads its own toggle
9
+ - Stop button now clears crashed processes (dead PIDs) instead of showing stale "running" state
10
+
3
11
  ## 1.2.0 (2026-03-28)
4
12
 
5
13
  ### AEO — AI Citability Audit (new feature)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "seo-intel",
3
- "version": "1.2.0",
3
+ "version": "1.2.3",
4
4
  "description": "Local Ahrefs-style SEO competitor intelligence. Crawl → SQLite → cloud analysis.",
5
5
  "type": "module",
6
6
  "license": "SEE LICENSE IN LICENSE",
@@ -1960,22 +1960,6 @@ function buildHtmlTemplate(data, opts = {}) {
1960
1960
  ` : ''}
1961
1961
  </div>
1962
1962
  <div class="es-controls" id="esControls${suffix}">
1963
- ${extractionStatus.liveProgress?.status === 'running' && extractionStatus.liveProgress?.command === 'crawl'
1964
- ? `<button class="es-btn running" id="btnCrawl${suffix}" onclick="startJob('crawl','${project}')" disabled>
1965
- <i class="fa-solid fa-spinner fa-spin"></i> Crawling\u2026
1966
- </button>`
1967
- : `<button class="es-btn" id="btnCrawl${suffix}" onclick="startJob('crawl','${project}')"${extractionStatus.liveProgress?.status === 'running' ? ' disabled' : ''}>
1968
- <i class="fa-solid fa-spider"></i> Crawl
1969
- </button>`
1970
- }
1971
- ${extractionStatus.liveProgress?.status === 'running' && extractionStatus.liveProgress?.command === 'extract'
1972
- ? `<button class="es-btn running" id="btnExtract${suffix}" onclick="startJob('extract','${project}')" disabled>
1973
- <i class="fa-solid fa-spinner fa-spin"></i> Extracting\u2026
1974
- </button>`
1975
- : `<button class="es-btn" id="btnExtract${suffix}" onclick="startJob('extract','${project}')"${extractionStatus.liveProgress?.status === 'running' ? ' disabled' : ''}>
1976
- <i class="fa-solid fa-brain"></i> Extract
1977
- </button>`
1978
- }
1979
1963
  <button class="es-btn es-btn-stop${extractionStatus.liveProgress?.status === 'running' ? ' active' : ''}" id="btnStop${suffix}" onclick="stopJob()">
1980
1964
  <i class="fa-solid fa-stop"></i> Stop
1981
1965
  </button>
@@ -2178,7 +2162,7 @@ function buildHtmlTemplate(data, opts = {}) {
2178
2162
  var extra = scope ? { scope: scope } : {};
2179
2163
  // Crawl/extract: read stealth toggle + update status bar
2180
2164
  if (cmd === 'crawl' || cmd === 'extract') {
2181
- var stealthEl = document.querySelector('[id^="stealthToggle"]');
2165
+ var stealthEl = btn.closest('.project-panel')?.querySelector('[id^="stealthToggle"]') || document.getElementById('stealthToggle' + suffix);
2182
2166
  if (stealthEl?.checked) extra.stealth = true;
2183
2167
  if (window._setButtonsState) window._setButtonsState(true, cmd);
2184
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
  }