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.
- package/CHANGELOG.md +29 -0
- package/analyses/aeo/index.js +252 -0
- package/analyses/aeo/scorer.js +254 -0
- package/analyses/blog-draft/index.js +227 -0
- package/analyses/blog-draft/prescorer.js +60 -0
- package/analyses/templates/cluster.js +209 -0
- package/analyses/templates/gsc-overlay.js +93 -0
- package/analyses/templates/index.js +425 -0
- package/analyses/templates/sampler.js +198 -0
- package/analyses/templates/scorer.js +149 -0
- package/analyses/templates/similarity.js +174 -0
- package/analysis/prompt-builder.js +272 -0
- package/analysis/topic-cluster-mapper.js +427 -0
- package/cli.js +124 -1
- package/extractor/qwen.js +558 -0
- package/lib/gate.js +1 -0
- package/package.json +4 -1
- package/reports/generate-html.js +183 -0
- package/server.js +6 -1
package/reports/generate-html.js
CHANGED
|
@@ -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, {
|