seo-intel 1.2.5 → 1.3.0

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.
@@ -1488,6 +1488,71 @@ function buildHtmlTemplate(data, opts = {}) {
1488
1488
  .export-btn:hover { border-color: var(--accent-gold); color: var(--accent-gold); }
1489
1489
  .export-btn i { margin-right: 5px; font-size: 0.6rem; }
1490
1490
  .export-btn.active { border-color: var(--accent-gold); color: var(--accent-gold); background: rgba(232,213,163,0.06); }
1491
+ .draft-dropdown { position: relative; }
1492
+ .draft-trigger { display: flex; align-items: center; width: 100%; }
1493
+ .draft-menu {
1494
+ display: none;
1495
+ position: absolute;
1496
+ top: calc(100% + 4px);
1497
+ left: 0; right: 0;
1498
+ background: #1a1a1a;
1499
+ border: 1px solid var(--accent-gold);
1500
+ border-radius: var(--radius);
1501
+ padding: 10px;
1502
+ z-index: 50;
1503
+ box-shadow: 0 8px 24px rgba(0,0,0,0.5);
1504
+ }
1505
+ .draft-menu.open { display: block; }
1506
+ .draft-menu-section {
1507
+ font-size: 0.58rem;
1508
+ color: var(--text-muted);
1509
+ text-transform: uppercase;
1510
+ letter-spacing: 0.5px;
1511
+ margin-bottom: 5px;
1512
+ font-family: var(--font-body);
1513
+ }
1514
+ .draft-option {
1515
+ display: flex;
1516
+ align-items: center;
1517
+ gap: 6px;
1518
+ padding: 5px 8px;
1519
+ font-size: 0.65rem;
1520
+ color: var(--text-secondary);
1521
+ cursor: pointer;
1522
+ border-radius: 4px;
1523
+ font-family: var(--font-body);
1524
+ }
1525
+ .draft-option:hover { background: rgba(232,213,163,0.06); color: var(--text-primary); }
1526
+ .draft-option input[type="radio"] { accent-color: var(--accent-gold); width: 12px; height: 12px; }
1527
+ .draft-option i { font-size: 0.6rem; width: 14px; text-align: center; }
1528
+ .draft-topic-input {
1529
+ width: 100%;
1530
+ background: #111;
1531
+ border: 1px solid var(--border-subtle);
1532
+ border-radius: 4px;
1533
+ padding: 6px 8px;
1534
+ color: var(--text-primary);
1535
+ font-size: 0.65rem;
1536
+ font-family: 'SF Mono', monospace;
1537
+ outline: none;
1538
+ box-sizing: border-box;
1539
+ }
1540
+ .draft-topic-input:focus { border-color: var(--accent-gold); }
1541
+ .draft-generate-btn {
1542
+ width: 100%;
1543
+ margin-top: 10px;
1544
+ padding: 8px;
1545
+ background: var(--accent-gold);
1546
+ color: var(--text-dark);
1547
+ border: none;
1548
+ border-radius: var(--radius);
1549
+ font-size: 0.68rem;
1550
+ font-weight: 600;
1551
+ cursor: pointer;
1552
+ font-family: var(--font-body);
1553
+ transition: opacity 0.15s;
1554
+ }
1555
+ .draft-generate-btn:hover { opacity: 0.9; }
1491
1556
  .export-viewer {
1492
1557
  flex: 1;
1493
1558
  padding: 12px;
@@ -2024,6 +2089,28 @@ function buildHtmlTemplate(data, opts = {}) {
2024
2089
  <button class="export-btn" data-export-cmd="export-actions" data-export-project="${project}" data-export-scope="competitive"><i class="fa-solid fa-users"></i> Competitive Gaps</button>
2025
2090
  <button class="export-btn" data-export-cmd="suggest-usecases" data-export-project="${project}"><i class="fa-solid fa-lightbulb"></i> Suggest What to Build</button>
2026
2091
  </div>
2092
+ <div class="export-sidebar-header" style="margin-top:12px;">
2093
+ <i class="fa-solid fa-pen-fancy"></i> Create
2094
+ </div>
2095
+ <div class="export-sidebar-btns">
2096
+ <div class="draft-dropdown" id="draftDropdown${suffix}">
2097
+ <button class="export-btn draft-trigger" id="draftTrigger${suffix}"><i class="fa-solid fa-file-pen"></i> Create a Draft <i class="fa-solid fa-chevron-down" style="font-size:0.55rem;margin-left:auto;opacity:0.5;"></i></button>
2098
+ <div class="draft-menu" id="draftMenu${suffix}">
2099
+ <div class="draft-menu-section">Type</div>
2100
+ <label class="draft-option"><input type="radio" name="draftType${suffix}" value="blog" checked /> <i class="fa-solid fa-blog"></i> Blog Post</label>
2101
+ <label class="draft-option"><input type="radio" name="draftType${suffix}" value="docs" disabled /> <i class="fa-solid fa-book"></i> Documentation <span style="font-size:0.55rem;opacity:0.4;margin-left:4px;">soon</span></label>
2102
+ <div class="draft-menu-section" style="margin-top:8px;">Topic <span style="font-size:0.55rem;opacity:0.4;">(optional)</span></div>
2103
+ <input type="text" id="draftTopic${suffix}" class="draft-topic-input" placeholder="e.g. solana rpc, site speed..." />
2104
+ <div class="draft-menu-section" style="margin-top:8px;">Language</div>
2105
+ <div style="display:flex;gap:6px;">
2106
+ <label class="draft-option" style="flex:1;"><input type="radio" name="draftLang${suffix}" value="en" checked /> EN</label>
2107
+ <label class="draft-option" style="flex:1;"><input type="radio" name="draftLang${suffix}" value="fi" /> FI</label>
2108
+ </div>
2109
+ <button class="draft-generate-btn" id="draftGenerate${suffix}" data-project="${project}"><i class="fa-solid fa-wand-magic-sparkles"></i> Generate Draft</button>
2110
+ </div>
2111
+ </div>
2112
+ <button class="export-btn" data-export-cmd="aeo" data-export-project="${project}"><i class="fa-solid fa-robot"></i> AI Citability Audit</button>
2113
+ </div>
2027
2114
  <div id="exportViewer${suffix}" class="export-viewer">
2028
2115
  <div style="color:#444;padding:20px 0;text-align:center;">
2029
2116
  <i class="fa-solid fa-file-export" style="font-size:1.2rem;margin-bottom:8px;display:block;"></i>
@@ -2245,6 +2332,102 @@ function buildHtmlTemplate(data, opts = {}) {
2245
2332
  });
2246
2333
  });
2247
2334
 
2335
+ // Draft dropdown
2336
+ var draftTrigger = document.getElementById('draftTrigger' + suffix);
2337
+ var draftMenu = document.getElementById('draftMenu' + suffix);
2338
+ var draftGenerate = document.getElementById('draftGenerate' + suffix);
2339
+ if (draftTrigger && draftMenu) {
2340
+ draftTrigger.addEventListener('click', function(e) {
2341
+ e.stopPropagation();
2342
+ draftMenu.classList.toggle('open');
2343
+ });
2344
+ document.addEventListener('click', function(e) {
2345
+ if (!draftMenu.contains(e.target) && e.target !== draftTrigger) {
2346
+ draftMenu.classList.remove('open');
2347
+ }
2348
+ });
2349
+ }
2350
+ if (draftGenerate) {
2351
+ draftGenerate.addEventListener('click', function() {
2352
+ if (running) return;
2353
+ var proj = draftGenerate.getAttribute('data-project');
2354
+ var typeEl = document.querySelector('input[name="draftType' + suffix + '"]:checked');
2355
+ var langEl = document.querySelector('input[name="draftLang' + suffix + '"]:checked');
2356
+ var topicEl = document.getElementById('draftTopic' + suffix);
2357
+ var draftType = typeEl ? typeEl.value : 'blog';
2358
+ var lang = langEl ? langEl.value : 'en';
2359
+ var topic = topicEl ? topicEl.value.trim() : '';
2360
+
2361
+ if (draftType !== 'blog') return; // docs not yet supported
2362
+
2363
+ draftMenu.classList.remove('open');
2364
+
2365
+ // Run blog-draft via terminal SSE
2366
+ var extra = { lang: lang };
2367
+ if (topic) extra.topic = topic;
2368
+
2369
+ var params = new URLSearchParams({ command: 'blog-draft' });
2370
+ params.set('project', proj);
2371
+ params.set('lang', lang);
2372
+ params.set('save', '1');
2373
+ if (topic) params.set('topic', topic);
2374
+
2375
+ if (!isServed) {
2376
+ var cmd = 'seo-intel blog-draft ' + proj + (topic ? ' --topic "' + topic + '"' : '') + ' --lang ' + lang + ' --save';
2377
+ if (exportViewer) {
2378
+ exportViewer.innerHTML = '<div style="color:var(--color-danger);padding:12px;">Not connected. Run in terminal:<br/><code style="color:var(--accent-gold);">' + cmd + '</code></div>';
2379
+ }
2380
+ return;
2381
+ }
2382
+
2383
+ if (exportViewer) exportViewer.innerHTML = '<div style="color:var(--text-muted);padding:20px;text-align:center;"><i class="fa-solid fa-wand-magic-sparkles fa-spin" style="margin-right:6px;color:var(--accent-gold);"></i>Generating AEO draft...</div>';
2384
+
2385
+ var mdContent = '';
2386
+ var es = new EventSource('/api/terminal?' + params.toString());
2387
+ running = true;
2388
+ status.textContent = 'generating draft...';
2389
+ status.style.color = 'var(--color-warning)';
2390
+
2391
+ es.onmessage = function(e) {
2392
+ try {
2393
+ var msg = JSON.parse(e.data);
2394
+ if (msg.type === 'stdout') mdContent += msg.data + '\\n';
2395
+ else if (msg.type === 'stderr') appendLine(msg.data, 'stderr');
2396
+ else if (msg.type === 'exit') {
2397
+ running = false;
2398
+ var code = msg.data?.code ?? msg.data;
2399
+ status.textContent = code === 0 ? 'draft saved' : 'failed';
2400
+ status.style.color = code === 0 ? 'var(--color-success)' : 'var(--color-danger)';
2401
+ es.close();
2402
+ if (exportViewer && mdContent.trim()) {
2403
+ var bt = String.fromCharCode(96);
2404
+ var codeRe = new RegExp(bt + '([^' + bt + ']+)' + bt, 'g');
2405
+ var html = mdContent
2406
+ .replace(/^### (.*$)/gm, '<h3>$1</h3>')
2407
+ .replace(/^## (.*$)/gm, '<h2>$1</h2>')
2408
+ .replace(/^# (.*$)/gm, '<h1>$1</h1>')
2409
+ .replace(/\\*\\*(.*?)\\*\\*/g, '<strong>$1</strong>')
2410
+ .replace(/^- (.*$)/gm, '<li>$1</li>')
2411
+ .replace(codeRe, '<code>$1</code>')
2412
+ .replace(/\\n/g, '<br/>');
2413
+ exportViewer.innerHTML = html;
2414
+ exportViewer.scrollTop = 0;
2415
+ } else if (exportViewer) {
2416
+ exportViewer.innerHTML = '<div style="color:var(--text-muted);">Draft generated — check reports/ folder.</div>';
2417
+ }
2418
+ }
2419
+ } catch (_) {}
2420
+ };
2421
+ es.onerror = function() {
2422
+ running = false;
2423
+ status.textContent = 'error';
2424
+ status.style.color = 'var(--color-danger)';
2425
+ es.close();
2426
+ if (exportViewer) exportViewer.innerHTML = '<div style="color:var(--color-danger);">Connection failed.</div>';
2427
+ };
2428
+ });
2429
+ }
2430
+
2248
2431
  // Input enter
2249
2432
  input.addEventListener('keydown', function(e) {
2250
2433
  if (e.key !== 'Enter') return;
package/server.js CHANGED
@@ -595,7 +595,8 @@ async function handleRequest(req, res) {
595
595
  // Whitelist allowed commands
596
596
  const ALLOWED = ['crawl', 'extract', 'analyze', 'export-actions', 'competitive-actions',
597
597
  'suggest-usecases', 'html', 'status', 'brief', 'keywords', 'report', 'guide',
598
- 'schemas', 'headings-audit', 'orphans', 'entities', 'friction', 'shallow', 'decay', 'export', 'templates'];
598
+ 'schemas', 'headings-audit', 'orphans', 'entities', 'friction', 'shallow', 'decay', 'export', 'templates',
599
+ 'aeo', 'blog-draft'];
599
600
 
600
601
  if (!command || !ALLOWED.includes(command)) {
601
602
  json(res, 400, { error: `Invalid command. Allowed: ${ALLOWED.join(', ')}` });
@@ -608,6 +609,10 @@ async function handleRequest(req, res) {
608
609
  if (params.get('stealth') === 'true') args.push('--stealth');
609
610
  if (params.get('scope')) args.push('--scope', params.get('scope'));
610
611
  if (params.get('format')) args.push('--format', params.get('format'));
612
+ if (params.get('topic')) args.push('--topic', params.get('topic'));
613
+ if (params.get('lang')) args.push('--lang', params.get('lang'));
614
+ if (params.get('model')) args.push('--model', params.get('model'));
615
+ if (params.has('save')) args.push('--save');
611
616
 
612
617
  // SSE headers
613
618
  res.writeHead(200, {