seo-intel 1.1.0 → 1.1.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "seo-intel",
3
- "version": "1.1.0",
3
+ "version": "1.1.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",
@@ -261,6 +261,44 @@ function buildHtmlTemplate(data, opts = {}) {
261
261
  }
262
262
 
263
263
  /* ─── Header Bar ─────────────────────────────────────────────────────── */
264
+ .update-banner {
265
+ padding: 10px 20px;
266
+ border-radius: var(--radius);
267
+ display: flex;
268
+ align-items: center;
269
+ gap: 12px;
270
+ font-size: 0.78rem;
271
+ margin-bottom: 12px;
272
+ }
273
+ .update-banner.update-normal {
274
+ background: rgba(232,213,163,0.06);
275
+ border: 1px solid rgba(232,213,163,0.2);
276
+ color: var(--accent-gold);
277
+ }
278
+ .update-banner.update-security {
279
+ background: rgba(220,80,80,0.08);
280
+ border: 1px solid rgba(220,80,80,0.25);
281
+ color: #ff6b6b;
282
+ }
283
+ .update-banner .update-version { font-family: var(--font-mono); font-weight: 600; }
284
+ .update-banner .update-changelog { font-size: 0.68rem; color: var(--text-muted); flex:1; }
285
+ .update-banner .update-btn {
286
+ padding: 5px 14px;
287
+ border-radius: 6px;
288
+ font-size: 0.7rem;
289
+ font-weight: 600;
290
+ border: 1px solid currentColor;
291
+ background: transparent;
292
+ color: inherit;
293
+ cursor: pointer;
294
+ white-space: nowrap;
295
+ }
296
+ .update-banner .update-btn:hover { background: rgba(255,255,255,0.06); }
297
+ .update-banner .update-dismiss {
298
+ cursor: pointer; opacity: 0.5; font-size: 0.7rem;
299
+ }
300
+ .update-banner .update-dismiss:hover { opacity: 1; }
301
+
264
302
  .header-bar {
265
303
  background: var(--bg-card);
266
304
  border: 1px solid var(--border-card);
@@ -1795,6 +1833,8 @@ function buildHtmlTemplate(data, opts = {}) {
1795
1833
  // ── Panel HTML (project-specific body content) ──
1796
1834
  const panelHtml = `
1797
1835
  <div class="project-panel" data-project="${project}">
1836
+ <!-- UPDATE BANNER (populated by JS if update available) -->
1837
+ <div id="updateBanner${suffix}" style="display:none;"></div>
1798
1838
  <!-- HEADER BAR -->
1799
1839
  <div class="header-bar" id="header">
1800
1840
  <div class="header-left">
@@ -3405,6 +3445,27 @@ function buildHtmlTemplate(data, opts = {}) {
3405
3445
  bg: '#111111', grid: '#222222', text: '#b8b8b8', muted: '#555555'
3406
3446
  };
3407
3447
 
3448
+ // ═══ UPDATE CHECK ═══
3449
+ (function() {
3450
+ if (!window.location.protocol.startsWith('http')) return;
3451
+ fetch('/api/update-check').then(r => r.json()).then(function(info) {
3452
+ if (!info.hasUpdate) return;
3453
+ var banner = document.getElementById('updateBanner${suffix}');
3454
+ if (!banner) return;
3455
+ var cls = info.security ? 'update-security' : 'update-normal';
3456
+ var icon = info.security ? 'fa-shield-halved' : 'fa-arrow-up';
3457
+ var changelogHtml = info.changelog ? '<span class="update-changelog">' + info.changelog.split('\\n')[0] + '</span>' : '';
3458
+ banner.style.display = 'block';
3459
+ banner.innerHTML = '<div class="update-banner ' + cls + '">' +
3460
+ '<i class="fa-solid ' + icon + '"></i>' +
3461
+ '<span class="update-version">' + info.current + ' → ' + info.latest + '</span>' +
3462
+ changelogHtml +
3463
+ '<button class="update-btn" onclick="navigator.clipboard.writeText(\\'npm update -g seo-intel\\');this.textContent=\\'Copied!\\';setTimeout(()=>this.textContent=\\'Update\\',2000)">Update</button>' +
3464
+ '<span class="update-dismiss" onclick="this.closest(\\'.update-banner\\').style.display=\\'none\\'"><i class="fa-solid fa-xmark"></i></span>' +
3465
+ '</div>';
3466
+ }).catch(function() {});
3467
+ })();
3468
+
3408
3469
  // ═══ LIVE DASHBOARD CONTROLS ═══
3409
3470
  (function() {
3410
3471
  const isServed = window.location.protocol.startsWith('http');
package/server.js CHANGED
@@ -3,6 +3,7 @@ import { readFileSync, writeFileSync, existsSync, readdirSync } from 'fs';
3
3
  import { spawn } from 'child_process';
4
4
  import { dirname, join, extname } from 'path';
5
5
  import { fileURLToPath } from 'url';
6
+ import { checkForUpdates, getUpdateInfo } from './lib/updater.js';
6
7
 
7
8
  const __dirname = dirname(fileURLToPath(import.meta.url));
8
9
  const PORT = parseInt(process.env.PORT || '3000', 10);
@@ -325,6 +326,17 @@ async function handleRequest(req, res) {
325
326
 
326
327
 
327
328
  // ─── API: Stop running job ───
329
+ // ─── API: Update check ───
330
+ if (req.method === 'GET' && path === '/api/update-check') {
331
+ try {
332
+ const info = await getUpdateInfo();
333
+ json(res, 200, info);
334
+ } catch (e) {
335
+ json(res, 200, { hasUpdate: false, error: e.message });
336
+ }
337
+ return;
338
+ }
339
+
328
340
  if (req.method === 'POST' && path === '/api/stop') {
329
341
  try {
330
342
  const progress = readProgress();
@@ -604,6 +616,9 @@ const server = createServer((req, res) => {
604
616
  });
605
617
  });
606
618
 
619
+ // Start background update check
620
+ checkForUpdates();
621
+
607
622
  server.listen(PORT, '127.0.0.1', () => {
608
623
  console.log(`\n SEO Intel Dashboard Server`);
609
624
  console.log(` http://localhost:${PORT}\n`);