seo-intel 1.5.39 → 1.5.45

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.
@@ -128,6 +128,17 @@ export function handleSetupRequest(req, res, url) {
128
128
  return true;
129
129
  }
130
130
 
131
+ // GET /api/setup/cron — read notify-cron install state
132
+ if (path === '/api/setup/cron' && method === 'GET') {
133
+ handleCronStatus(req, res);
134
+ return true;
135
+ }
136
+ // POST /api/setup/cron — install / remove
137
+ if (path === '/api/setup/cron' && method === 'POST') {
138
+ handleCronAction(req, res);
139
+ return true;
140
+ }
141
+
131
142
  // POST /api/setup/save-env — save a key to .env
132
143
  if (path === '/api/setup/save-env' && method === 'POST') {
133
144
  handleSaveEnv(req, res);
@@ -341,6 +352,34 @@ async function handlePingOllama(req, res) {
341
352
  }
342
353
  }
343
354
 
355
+ async function handleCronStatus(req, res) {
356
+ try {
357
+ const { getNotifyCronStatus } = await import('../lib/cron.js');
358
+ jsonResponse(res, getNotifyCronStatus());
359
+ } catch (err) {
360
+ jsonResponse(res, { error: err.message }, 500);
361
+ }
362
+ }
363
+
364
+ async function handleCronAction(req, res) {
365
+ try {
366
+ const body = await readBody(req);
367
+ const { action, schedule, openOnFire } = body || {};
368
+ const { installNotifyCron, removeNotifyCron, getNotifyCronStatus } = await import('../lib/cron.js');
369
+ if (action === 'install') {
370
+ const result = installNotifyCron({ schedule, openOnFire: !!openOnFire });
371
+ jsonResponse(res, { ...result, status: getNotifyCronStatus() });
372
+ } else if (action === 'remove') {
373
+ const result = removeNotifyCron();
374
+ jsonResponse(res, { ...result, status: getNotifyCronStatus() });
375
+ } else {
376
+ jsonResponse(res, { error: 'action must be "install" or "remove"' }, 400);
377
+ }
378
+ } catch (err) {
379
+ jsonResponse(res, { error: err.message }, 500);
380
+ }
381
+ }
382
+
344
383
  async function handleSaveEnv(req, res) {
345
384
  try {
346
385
  const body = await readBody(req);
package/setup/wizard.html CHANGED
@@ -1862,6 +1862,21 @@ input::placeholder {
1862
1862
  <!-- Populated by JS -->
1863
1863
  </table>
1864
1864
 
1865
+ <!-- ── Daily notification scheduler (v1.5.40) ────────────────────────── -->
1866
+ <div id="notifyCronCard" style="margin-top:20px; padding:14px 16px; background:var(--bg-elevated); border:1px solid rgba(59,130,246,0.18); border-radius:var(--radius); display:flex; gap:14px; align-items:center; flex-wrap:wrap;">
1867
+ <i class="fa-solid fa-bell" style="font-size:1rem; color:#3b82f6;"></i>
1868
+ <div style="flex:1; min-width:200px;">
1869
+ <div style="font-size:0.8rem; font-weight:600; color:var(--text-primary); margin-bottom:2px;">Daily problem notifications</div>
1870
+ <div id="notifyCronHint" style="font-size:0.66rem; color:var(--text-muted); line-height:1.4;">
1871
+ Fires a native macOS / Linux notification each morning for projects with pending criticals or warns.
1872
+ </div>
1873
+ </div>
1874
+ <div id="notifyCronStatus" style="font-size:0.66rem; color:var(--text-muted);">…</div>
1875
+ <button id="notifyCronToggleBtn" class="btn btn-sm" onclick="toggleNotifyCron()" style="padding:5px 12px; font-size:0.7rem; min-width:90px;">
1876
+ <i class="fa-solid fa-spinner fa-spin"></i>
1877
+ </button>
1878
+ </div>
1879
+
1865
1880
  <h3 style="font-family:var(--font-display); font-size:0.85rem; color:var(--text-primary); margin: 20px 0 12px; font-weight:600;">
1866
1881
  <i class="fa-solid fa-rocket" style="color:var(--accent-gold); margin-right:6px;"></i> What's Next
1867
1882
  </h3>
@@ -3409,6 +3424,64 @@ input::placeholder {
3409
3424
  }
3410
3425
 
3411
3426
  goToStep(6);
3427
+ loadNotifyCronStatus();
3428
+ };
3429
+
3430
+ // ── Daily notification cron (v1.5.40) ──────────────────────────────────
3431
+ let _cronState = null;
3432
+ async function loadNotifyCronStatus() {
3433
+ const statusEl = document.getElementById('notifyCronStatus');
3434
+ const btn = document.getElementById('notifyCronToggleBtn');
3435
+ const hint = document.getElementById('notifyCronHint');
3436
+ if (!statusEl || !btn) return;
3437
+ try {
3438
+ const s = await API.get('/api/setup/cron');
3439
+ _cronState = s;
3440
+ if (s.platform === 'win32') {
3441
+ statusEl.textContent = 'Windows: use Task Scheduler';
3442
+ btn.disabled = true;
3443
+ btn.innerHTML = 'N/A';
3444
+ hint.textContent = 'Cron auto-install is macOS / Linux only — set up a daily Task Scheduler entry manually.';
3445
+ return;
3446
+ }
3447
+ if (s.installed) {
3448
+ statusEl.innerHTML = `<span style="color:var(--color-success);"><i class="fa-solid fa-check"></i> Active · ${s.schedule}</span>`;
3449
+ btn.innerHTML = '<i class="fa-solid fa-bell-slash"></i> Disable';
3450
+ btn.style.borderColor = 'rgba(244,123,93,0.4)';
3451
+ btn.style.color = '#f47b5d';
3452
+ } else {
3453
+ statusEl.innerHTML = `<span style="color:var(--text-muted);">Not scheduled</span>`;
3454
+ btn.innerHTML = '<i class="fa-solid fa-bell"></i> Enable';
3455
+ btn.style.borderColor = 'rgba(59,130,246,0.4)';
3456
+ btn.style.color = '#3b82f6';
3457
+ }
3458
+ btn.disabled = false;
3459
+ } catch (err) {
3460
+ statusEl.textContent = 'Status check failed';
3461
+ btn.disabled = true;
3462
+ }
3463
+ }
3464
+
3465
+ window.toggleNotifyCron = async function() {
3466
+ const btn = document.getElementById('notifyCronToggleBtn');
3467
+ if (!btn || btn.disabled) return;
3468
+ btn.disabled = true;
3469
+ btn.innerHTML = '<i class="fa-solid fa-spinner fa-spin"></i>';
3470
+ const action = _cronState?.installed ? 'remove' : 'install';
3471
+ try {
3472
+ const r = await API.post('/api/setup/cron', { action, schedule: '0 9 * * *' });
3473
+ if (r.ok === false && r.error) {
3474
+ document.getElementById('notifyCronStatus').textContent = 'Error: ' + r.error;
3475
+ btn.disabled = false;
3476
+ btn.innerHTML = '<i class="fa-solid fa-triangle-exclamation"></i> Retry';
3477
+ return;
3478
+ }
3479
+ await loadNotifyCronStatus();
3480
+ } catch (err) {
3481
+ document.getElementById('notifyCronStatus').textContent = 'Error: ' + err.message;
3482
+ btn.disabled = false;
3483
+ btn.innerHTML = '<i class="fa-solid fa-triangle-exclamation"></i> Retry';
3484
+ }
3412
3485
  };
3413
3486
 
3414
3487
  window.copyCli = function() {