seo-intel 1.3.0 → 1.3.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/CHANGELOG.md +13 -0
- package/cli.js +137 -70
- package/package.json +1 -1
- package/reports/generate-html.js +84 -4
- package/server.js +11 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.3.1 (2026-04-02)
|
|
4
|
+
|
|
5
|
+
### Fixes
|
|
6
|
+
- **AI Citability Audit** now renders output in dashboard export viewer (was showing "No output")
|
|
7
|
+
- AEO command accepts `--format markdown|json|brief` for structured output
|
|
8
|
+
- Dashboard export viewer captures stderr — command errors are now visible instead of silent
|
|
9
|
+
|
|
10
|
+
### CI
|
|
11
|
+
- Added job-level timeout (15 min) — prevents 6-hour runaway jobs
|
|
12
|
+
- Cross-platform path handling — Windows CI no longer fails on backslash paths
|
|
13
|
+
- Playwright auto-installed for mock crawl test
|
|
14
|
+
- Step-level timeouts on crawl, setup wizard, and server tests
|
|
15
|
+
|
|
3
16
|
## 1.3.0 (2026-04-01)
|
|
4
17
|
|
|
5
18
|
### New Feature: AEO Blog Draft Generator
|
package/cli.js
CHANGED
|
@@ -3869,24 +3869,30 @@ program
|
|
|
3869
3869
|
.alias('citability')
|
|
3870
3870
|
.description('AI Citability Audit — score every page for how well AI assistants can cite it')
|
|
3871
3871
|
.option('--target-only', 'Only score target domain (skip competitors)')
|
|
3872
|
+
.option('--format <type>', 'Output format: brief or json', 'brief')
|
|
3872
3873
|
.option('--save', 'Save report to reports/')
|
|
3873
3874
|
.action(async (project, opts) => {
|
|
3874
3875
|
if (!requirePro('aeo')) return;
|
|
3875
3876
|
const db = getDb();
|
|
3876
3877
|
const config = loadConfig(project);
|
|
3878
|
+
const isBrief = opts.format !== 'json';
|
|
3877
3879
|
|
|
3878
|
-
|
|
3880
|
+
if (!isBrief) {
|
|
3881
|
+
// JSON mode — skip header
|
|
3882
|
+
} else {
|
|
3883
|
+
printAttackHeader('🤖 AEO — AI Citability Audit', project);
|
|
3884
|
+
}
|
|
3879
3885
|
|
|
3880
3886
|
const { runAeoAnalysis, persistAeoScores, upsertCitabilityInsights } = await import('./analyses/aeo/index.js');
|
|
3881
3887
|
|
|
3882
3888
|
const results = runAeoAnalysis(db, project, {
|
|
3883
3889
|
includeCompetitors: !opts.targetOnly,
|
|
3884
|
-
log: (msg) => console.log(chalk.gray(msg)),
|
|
3890
|
+
log: (msg) => isBrief ? console.log(chalk.gray(msg)) : null,
|
|
3885
3891
|
});
|
|
3886
3892
|
|
|
3887
3893
|
if (!results.target.length && !results.competitors.size) {
|
|
3888
|
-
console.log(chalk.yellow('\n ⚠️ No pages with body_text found.'));
|
|
3889
|
-
console.log(chalk.gray(' Run: seo-intel crawl ' + project + ' (crawl stores body text since v1.1.6)\n'));
|
|
3894
|
+
console.log(isBrief ? chalk.yellow('\n ⚠️ No pages with body_text found.') : 'No pages with body_text found.');
|
|
3895
|
+
console.log(isBrief ? chalk.gray(' Run: seo-intel crawl ' + project + ' (crawl stores body text since v1.1.6)\n') : 'Run: seo-intel crawl ' + project);
|
|
3890
3896
|
return;
|
|
3891
3897
|
}
|
|
3892
3898
|
|
|
@@ -3895,88 +3901,149 @@ program
|
|
|
3895
3901
|
upsertCitabilityInsights(db, project, results.target);
|
|
3896
3902
|
|
|
3897
3903
|
const { summary } = results;
|
|
3904
|
+
const { tierCounts } = summary;
|
|
3905
|
+
const worst = results.target.filter(r => r.score < 55).slice(0, 10);
|
|
3906
|
+
const best = results.target.filter(r => r.score >= 55).slice(-5).reverse();
|
|
3898
3907
|
|
|
3899
|
-
// ──
|
|
3900
|
-
|
|
3901
|
-
console.log(chalk.bold(' 📊 Citability Summary'));
|
|
3902
|
-
console.log('');
|
|
3908
|
+
// ── Markdown output (used by dashboard export viewer) ──
|
|
3909
|
+
if (opts.format === 'markdown') {
|
|
3903
3910
|
|
|
3904
|
-
|
|
3905
|
-
|
|
3906
|
-
|
|
3907
|
-
if (
|
|
3908
|
-
|
|
3909
|
-
|
|
3911
|
+
console.log('# AEO — AI Citability Audit\n');
|
|
3912
|
+
console.log(`## Summary\n`);
|
|
3913
|
+
console.log(`- **Target average:** ${summary.avgTargetScore}/100`);
|
|
3914
|
+
if (summary.competitorPages > 0) {
|
|
3915
|
+
console.log(`- **Competitor average:** ${summary.avgCompetitorScore}/100`);
|
|
3916
|
+
const delta = summary.scoreDelta;
|
|
3917
|
+
console.log(`- **Delta:** ${delta > 0 ? '+' : ''}${delta}`);
|
|
3918
|
+
}
|
|
3919
|
+
console.log(`- **Pages scored:** ${results.target.length}\n`);
|
|
3920
|
+
|
|
3921
|
+
console.log(`## Tier Breakdown\n`);
|
|
3922
|
+
console.log(`- Excellent (75+): ${tierCounts.excellent}`);
|
|
3923
|
+
console.log(`- Good (55-74): ${tierCounts.good}`);
|
|
3924
|
+
console.log(`- Needs work (35-54): ${tierCounts.needs_work}`);
|
|
3925
|
+
console.log(`- Poor (<35): ${tierCounts.poor}\n`);
|
|
3926
|
+
|
|
3927
|
+
if (summary.weakestSignals.length) {
|
|
3928
|
+
console.log(`## Weakest Signals\n`);
|
|
3929
|
+
for (const s of summary.weakestSignals) {
|
|
3930
|
+
const pct = Math.round(s.avg);
|
|
3931
|
+
const bar = '█'.repeat(Math.round(pct / 5)) + '░'.repeat(20 - Math.round(pct / 5));
|
|
3932
|
+
console.log(`- **${s.signal}** ${bar} ${pct}/100`);
|
|
3933
|
+
}
|
|
3934
|
+
console.log('');
|
|
3935
|
+
}
|
|
3910
3936
|
|
|
3911
|
-
|
|
3912
|
-
|
|
3913
|
-
|
|
3914
|
-
|
|
3915
|
-
|
|
3916
|
-
|
|
3917
|
-
|
|
3918
|
-
|
|
3937
|
+
if (worst.length) {
|
|
3938
|
+
console.log(`## Pages Needing Work\n`);
|
|
3939
|
+
for (const p of worst) {
|
|
3940
|
+
const path = p.url.replace(/https?:\/\/[^/]+/, '') || '/';
|
|
3941
|
+
const weakest = Object.entries(p.breakdown)
|
|
3942
|
+
.sort(([, a], [, b]) => a - b)
|
|
3943
|
+
.slice(0, 2)
|
|
3944
|
+
.map(([k]) => k.replace(/_/g, ' '));
|
|
3945
|
+
console.log(`- **${path.slice(0, 60)}** — ${p.score}/100 (weak: ${weakest.join(', ')})`);
|
|
3946
|
+
}
|
|
3947
|
+
console.log('');
|
|
3948
|
+
}
|
|
3919
3949
|
|
|
3920
|
-
|
|
3921
|
-
|
|
3922
|
-
|
|
3923
|
-
|
|
3924
|
-
|
|
3925
|
-
|
|
3926
|
-
|
|
3950
|
+
if (best.length) {
|
|
3951
|
+
console.log(`## Top Citable Pages\n`);
|
|
3952
|
+
for (const p of best) {
|
|
3953
|
+
const path = p.url.replace(/https?:\/\/[^/]+/, '') || '/';
|
|
3954
|
+
console.log(`- **${path.slice(0, 60)}** — ${p.score}/100 (${p.aiIntents.join(', ')})`);
|
|
3955
|
+
}
|
|
3956
|
+
console.log('');
|
|
3957
|
+
}
|
|
3927
3958
|
|
|
3928
|
-
|
|
3929
|
-
|
|
3930
|
-
|
|
3931
|
-
|
|
3932
|
-
|
|
3933
|
-
|
|
3934
|
-
|
|
3959
|
+
console.log(`## Actions\n`);
|
|
3960
|
+
if (tierCounts.poor > 0) {
|
|
3961
|
+
console.log(`1. Fix ${tierCounts.poor} poor-scoring pages — add structured headings, Q&A format, entity depth`);
|
|
3962
|
+
}
|
|
3963
|
+
if (summary.weakestSignals.length && summary.weakestSignals[0].avg < 40) {
|
|
3964
|
+
console.log(`2. Site-wide weakness: "${summary.weakestSignals[0].signal}" — systematically improve across all pages`);
|
|
3965
|
+
}
|
|
3966
|
+
if (summary.scoreDelta < 0) {
|
|
3967
|
+
console.log(`3. Competitors are ${Math.abs(summary.scoreDelta)} points ahead — prioritise top-traffic pages first`);
|
|
3935
3968
|
}
|
|
3936
3969
|
console.log('');
|
|
3937
|
-
}
|
|
3970
|
+
} else if (opts.format === 'json') {
|
|
3971
|
+
// ── JSON output ──
|
|
3972
|
+
console.log(JSON.stringify({ summary, target: results.target, competitors: [...results.competitors.entries()] }, null, 2));
|
|
3973
|
+
} else {
|
|
3974
|
+
// ── Rich CLI output (default brief format) ──
|
|
3975
|
+
const scoreFmt = (s) => {
|
|
3976
|
+
if (s >= 75) return chalk.bold.green(s + '/100');
|
|
3977
|
+
if (s >= 55) return chalk.bold.yellow(s + '/100');
|
|
3978
|
+
if (s >= 35) return chalk.hex('#ff8c00')(s + '/100');
|
|
3979
|
+
return chalk.bold.red(s + '/100');
|
|
3980
|
+
};
|
|
3938
3981
|
|
|
3939
|
-
// ── Worst pages (actionable) ──
|
|
3940
|
-
const worst = results.target.filter(r => r.score < 55).slice(0, 10);
|
|
3941
|
-
if (worst.length) {
|
|
3942
|
-
console.log(chalk.bold.red(' ⚡ Pages Needing Work'));
|
|
3943
3982
|
console.log('');
|
|
3944
|
-
|
|
3945
|
-
|
|
3946
|
-
|
|
3947
|
-
|
|
3948
|
-
|
|
3949
|
-
|
|
3950
|
-
|
|
3951
|
-
console.log(
|
|
3983
|
+
console.log(chalk.bold(' 📊 Citability Summary'));
|
|
3984
|
+
console.log('');
|
|
3985
|
+
console.log(` Target average: ${scoreFmt(summary.avgTargetScore)}`);
|
|
3986
|
+
if (summary.competitorPages > 0) {
|
|
3987
|
+
console.log(` Competitor average: ${scoreFmt(summary.avgCompetitorScore)}`);
|
|
3988
|
+
const delta = summary.scoreDelta;
|
|
3989
|
+
const deltaStr = delta > 0 ? chalk.green(`+${delta}`) : delta < 0 ? chalk.red(`${delta}`) : chalk.gray('0');
|
|
3990
|
+
console.log(` Delta: ${deltaStr}`);
|
|
3952
3991
|
}
|
|
3953
3992
|
console.log('');
|
|
3954
|
-
}
|
|
3955
3993
|
|
|
3956
|
-
|
|
3957
|
-
|
|
3958
|
-
|
|
3959
|
-
console.log(chalk.
|
|
3994
|
+
console.log(` ${chalk.green('●')} Excellent (75+): ${tierCounts.excellent}`);
|
|
3995
|
+
console.log(` ${chalk.yellow('●')} Good (55-74): ${tierCounts.good}`);
|
|
3996
|
+
console.log(` ${chalk.hex('#ff8c00')('●')} Needs work (35-54): ${tierCounts.needs_work}`);
|
|
3997
|
+
console.log(` ${chalk.red('●')} Poor (<35): ${tierCounts.poor}`);
|
|
3960
3998
|
console.log('');
|
|
3961
|
-
|
|
3962
|
-
|
|
3963
|
-
console.log(
|
|
3999
|
+
|
|
4000
|
+
if (summary.weakestSignals.length) {
|
|
4001
|
+
console.log(chalk.bold(' 🔍 Weakest Signals (target average)'));
|
|
4002
|
+
console.log('');
|
|
4003
|
+
for (const s of summary.weakestSignals) {
|
|
4004
|
+
const bar = '█'.repeat(Math.round(s.avg / 5)) + chalk.gray('░'.repeat(20 - Math.round(s.avg / 5)));
|
|
4005
|
+
console.log(` ${s.signal.padEnd(20)} ${bar} ${s.avg}/100`);
|
|
4006
|
+
}
|
|
4007
|
+
console.log('');
|
|
3964
4008
|
}
|
|
3965
|
-
console.log('');
|
|
3966
|
-
}
|
|
3967
4009
|
|
|
3968
|
-
|
|
3969
|
-
|
|
3970
|
-
|
|
3971
|
-
|
|
3972
|
-
|
|
3973
|
-
|
|
3974
|
-
|
|
3975
|
-
|
|
3976
|
-
|
|
3977
|
-
|
|
4010
|
+
if (worst.length) {
|
|
4011
|
+
console.log(chalk.bold.red(' ⚡ Pages Needing Work'));
|
|
4012
|
+
console.log('');
|
|
4013
|
+
for (const p of worst) {
|
|
4014
|
+
const path = p.url.replace(/https?:\/\/[^/]+/, '') || '/';
|
|
4015
|
+
const weakest = Object.entries(p.breakdown)
|
|
4016
|
+
.sort(([, a], [, b]) => a - b)
|
|
4017
|
+
.slice(0, 2)
|
|
4018
|
+
.map(([k]) => k.replace(/_/g, ' '));
|
|
4019
|
+
console.log(` ${scoreFmt(p.score)} ${chalk.bold(path.slice(0, 50))}`);
|
|
4020
|
+
console.log(chalk.gray(` Weak: ${weakest.join(', ')}`));
|
|
4021
|
+
}
|
|
4022
|
+
console.log('');
|
|
4023
|
+
}
|
|
4024
|
+
|
|
4025
|
+
if (best.length) {
|
|
4026
|
+
console.log(chalk.bold.green(' ✨ Top Citable Pages'));
|
|
4027
|
+
console.log('');
|
|
4028
|
+
for (const p of best) {
|
|
4029
|
+
const path = p.url.replace(/https?:\/\/[^/]+/, '') || '/';
|
|
4030
|
+
console.log(` ${scoreFmt(p.score)} ${chalk.bold(path.slice(0, 50))} ${chalk.gray(p.aiIntents.join(', '))}`);
|
|
4031
|
+
}
|
|
4032
|
+
console.log('');
|
|
4033
|
+
}
|
|
4034
|
+
|
|
4035
|
+
console.log(chalk.bold.green(' 💡 Actions:'));
|
|
4036
|
+
if (tierCounts.poor > 0) {
|
|
4037
|
+
console.log(chalk.green(` 1. Fix ${tierCounts.poor} poor-scoring pages — add structured headings, Q&A format, entity depth`));
|
|
4038
|
+
}
|
|
4039
|
+
if (summary.weakestSignals.length && summary.weakestSignals[0].avg < 40) {
|
|
4040
|
+
console.log(chalk.green(` 2. Site-wide weakness: "${summary.weakestSignals[0].signal}" — systematically improve across all pages`));
|
|
4041
|
+
}
|
|
4042
|
+
if (summary.scoreDelta < 0) {
|
|
4043
|
+
console.log(chalk.green(` 3. Competitors are ${Math.abs(summary.scoreDelta)} points ahead — prioritise top-traffic pages first`));
|
|
4044
|
+
}
|
|
4045
|
+
console.log('');
|
|
3978
4046
|
}
|
|
3979
|
-
console.log('');
|
|
3980
4047
|
|
|
3981
4048
|
// ── Regenerate dashboard ──
|
|
3982
4049
|
try {
|
package/package.json
CHANGED
package/reports/generate-html.js
CHANGED
|
@@ -1553,6 +1553,43 @@ function buildHtmlTemplate(data, opts = {}) {
|
|
|
1553
1553
|
transition: opacity 0.15s;
|
|
1554
1554
|
}
|
|
1555
1555
|
.draft-generate-btn:hover { opacity: 0.9; }
|
|
1556
|
+
.export-expand-btn {
|
|
1557
|
+
position: absolute;
|
|
1558
|
+
top: 6px;
|
|
1559
|
+
right: 6px;
|
|
1560
|
+
z-index: 10;
|
|
1561
|
+
background: rgba(255,255,255,0.06);
|
|
1562
|
+
border: 1px solid var(--border-subtle);
|
|
1563
|
+
color: var(--text-muted);
|
|
1564
|
+
width: 24px; height: 24px;
|
|
1565
|
+
border-radius: 4px;
|
|
1566
|
+
cursor: pointer;
|
|
1567
|
+
display: flex;
|
|
1568
|
+
align-items: center;
|
|
1569
|
+
justify-content: center;
|
|
1570
|
+
font-size: 0.55rem;
|
|
1571
|
+
transition: all 0.15s;
|
|
1572
|
+
}
|
|
1573
|
+
.export-expand-btn:hover { border-color: var(--accent-gold); color: var(--accent-gold); }
|
|
1574
|
+
.export-viewer-expanded {
|
|
1575
|
+
position: fixed !important;
|
|
1576
|
+
top: 5vh; left: 5vw; right: 5vw; bottom: 5vh;
|
|
1577
|
+
max-height: none !important;
|
|
1578
|
+
z-index: 9999;
|
|
1579
|
+
background: #111;
|
|
1580
|
+
border: 1px solid var(--accent-gold);
|
|
1581
|
+
border-radius: var(--radius);
|
|
1582
|
+
padding: 24px;
|
|
1583
|
+
overflow-y: auto;
|
|
1584
|
+
box-shadow: 0 0 80px rgba(0,0,0,0.8);
|
|
1585
|
+
}
|
|
1586
|
+
.export-viewer-backdrop {
|
|
1587
|
+
position: fixed;
|
|
1588
|
+
inset: 0;
|
|
1589
|
+
background: rgba(0,0,0,0.7);
|
|
1590
|
+
z-index: 9998;
|
|
1591
|
+
cursor: pointer;
|
|
1592
|
+
}
|
|
1556
1593
|
.export-viewer {
|
|
1557
1594
|
flex: 1;
|
|
1558
1595
|
padding: 12px;
|
|
@@ -2082,6 +2119,7 @@ function buildHtmlTemplate(data, opts = {}) {
|
|
|
2082
2119
|
<div class="export-sidebar">
|
|
2083
2120
|
<div class="export-sidebar-header">
|
|
2084
2121
|
<i class="fa-solid fa-file-export"></i> Exports
|
|
2122
|
+
<span style="margin-left:auto;font-size:.55rem;color:var(--text-muted);font-weight:400;letter-spacing:0;">→ reports/</span>
|
|
2085
2123
|
</div>
|
|
2086
2124
|
${pro ? `
|
|
2087
2125
|
<div class="export-sidebar-btns">
|
|
@@ -2111,10 +2149,16 @@ function buildHtmlTemplate(data, opts = {}) {
|
|
|
2111
2149
|
</div>
|
|
2112
2150
|
<button class="export-btn" data-export-cmd="aeo" data-export-project="${project}"><i class="fa-solid fa-robot"></i> AI Citability Audit</button>
|
|
2113
2151
|
</div>
|
|
2114
|
-
<div
|
|
2115
|
-
<div style="
|
|
2116
|
-
<i class="fa-solid fa-
|
|
2117
|
-
|
|
2152
|
+
<div style="position:relative;">
|
|
2153
|
+
<div id="exportSaveStatus${suffix}" style="display:none;padding:4px 10px;font-size:.6rem;color:var(--color-success);background:rgba(80,200,120,0.06);border-bottom:1px solid rgba(80,200,120,0.15);font-family:'SF Mono',monospace;">
|
|
2154
|
+
<i class="fa-solid fa-check" style="margin-right:4px;"></i><span></span>
|
|
2155
|
+
</div>
|
|
2156
|
+
<button id="exportExpand${suffix}" class="export-expand-btn" title="Expand viewer"><i class="fa-solid fa-expand"></i></button>
|
|
2157
|
+
<div id="exportViewer${suffix}" class="export-viewer">
|
|
2158
|
+
<div style="color:#444;padding:20px 0;text-align:center;">
|
|
2159
|
+
<i class="fa-solid fa-file-export" style="font-size:1.2rem;margin-bottom:8px;display:block;"></i>
|
|
2160
|
+
Click an export to generate an<br/>implementation-ready action brief.
|
|
2161
|
+
</div>
|
|
2118
2162
|
</div>
|
|
2119
2163
|
</div>
|
|
2120
2164
|
` : `
|
|
@@ -2298,6 +2342,7 @@ function buildHtmlTemplate(data, opts = {}) {
|
|
|
2298
2342
|
try {
|
|
2299
2343
|
const msg = JSON.parse(e.data);
|
|
2300
2344
|
if (msg.type === 'stdout') mdContent += msg.data + '\\n';
|
|
2345
|
+
else if (msg.type === 'stderr') mdContent += msg.data + '\\n';
|
|
2301
2346
|
else if (msg.type === 'exit') {
|
|
2302
2347
|
running = false;
|
|
2303
2348
|
status.textContent = 'done';
|
|
@@ -2319,6 +2364,14 @@ function buildHtmlTemplate(data, opts = {}) {
|
|
|
2319
2364
|
exportViewer.innerHTML = html || '<div style="color:var(--text-muted);">No output.</div>';
|
|
2320
2365
|
exportViewer.scrollTop = 0;
|
|
2321
2366
|
}
|
|
2367
|
+
// Show save status
|
|
2368
|
+
var saveEl = document.getElementById('exportSaveStatus' + suffix);
|
|
2369
|
+
if (saveEl && code === 0) {
|
|
2370
|
+
var slugName = cmd === 'suggest-usecases' ? 'suggestions' : (scope || 'all');
|
|
2371
|
+
var dateStr = new Date().toISOString().slice(0, 10);
|
|
2372
|
+
saveEl.style.display = 'block';
|
|
2373
|
+
saveEl.querySelector('span').textContent = 'Saved → reports/' + proj + '-' + slugName + '-' + dateStr + '.md';
|
|
2374
|
+
}
|
|
2322
2375
|
}
|
|
2323
2376
|
} catch (_) {}
|
|
2324
2377
|
};
|
|
@@ -2428,6 +2481,33 @@ function buildHtmlTemplate(data, opts = {}) {
|
|
|
2428
2481
|
});
|
|
2429
2482
|
}
|
|
2430
2483
|
|
|
2484
|
+
// Expand viewer button
|
|
2485
|
+
var expandBtn = document.getElementById('exportExpand' + suffix);
|
|
2486
|
+
if (expandBtn && exportViewer) {
|
|
2487
|
+
expandBtn.addEventListener('click', function() {
|
|
2488
|
+
if (exportViewer.classList.contains('export-viewer-expanded')) {
|
|
2489
|
+
// Collapse
|
|
2490
|
+
exportViewer.classList.remove('export-viewer-expanded');
|
|
2491
|
+
expandBtn.innerHTML = '<i class="fa-solid fa-expand"></i>';
|
|
2492
|
+
var bd = document.getElementById('exportBackdrop' + suffix);
|
|
2493
|
+
if (bd) bd.remove();
|
|
2494
|
+
} else {
|
|
2495
|
+
// Expand
|
|
2496
|
+
exportViewer.classList.add('export-viewer-expanded');
|
|
2497
|
+
expandBtn.innerHTML = '<i class="fa-solid fa-compress"></i>';
|
|
2498
|
+
var bd = document.createElement('div');
|
|
2499
|
+
bd.id = 'exportBackdrop' + suffix;
|
|
2500
|
+
bd.className = 'export-viewer-backdrop';
|
|
2501
|
+
document.body.appendChild(bd);
|
|
2502
|
+
bd.addEventListener('click', function() {
|
|
2503
|
+
exportViewer.classList.remove('export-viewer-expanded');
|
|
2504
|
+
expandBtn.innerHTML = '<i class="fa-solid fa-expand"></i>';
|
|
2505
|
+
bd.remove();
|
|
2506
|
+
});
|
|
2507
|
+
}
|
|
2508
|
+
});
|
|
2509
|
+
}
|
|
2510
|
+
|
|
2431
2511
|
// Input enter
|
|
2432
2512
|
input.addEventListener('keydown', function(e) {
|
|
2433
2513
|
if (e.key !== 'Enter') return;
|
package/server.js
CHANGED
|
@@ -614,6 +614,17 @@ async function handleRequest(req, res) {
|
|
|
614
614
|
if (params.get('model')) args.push('--model', params.get('model'));
|
|
615
615
|
if (params.has('save')) args.push('--save');
|
|
616
616
|
|
|
617
|
+
// Auto-save exports from dashboard to reports/
|
|
618
|
+
const EXPORT_CMDS = ['export-actions', 'suggest-usecases', 'competitive-actions'];
|
|
619
|
+
if (EXPORT_CMDS.includes(command) && project) {
|
|
620
|
+
const scope = params.get('scope') || 'all';
|
|
621
|
+
const ts = new Date().toISOString().slice(0, 10);
|
|
622
|
+
const slug = command === 'suggest-usecases' ? 'suggestions' : scope;
|
|
623
|
+
const outFile = join(__dirname, 'reports', `${project}-${slug}-${ts}.md`);
|
|
624
|
+
args.push('--output', outFile);
|
|
625
|
+
args.push('--format', 'brief');
|
|
626
|
+
}
|
|
627
|
+
|
|
617
628
|
// SSE headers
|
|
618
629
|
res.writeHead(200, {
|
|
619
630
|
'Content-Type': 'text/event-stream',
|