seo-intel 1.1.3 → 1.1.5
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/Setup SEO Intel.command +12 -24
- package/package.json +10 -2
- package/reports/generate-html.js +56 -2
- package/server.js +9 -0
- package/setup/openclaw-bridge.js +29 -3
- package/setup/wizard.html +2 -0
- package/setup/ROADMAP.md +0 -109
package/Setup SEO Intel.command
CHANGED
|
@@ -1,27 +1,15 @@
|
|
|
1
1
|
#!/bin/bash
|
|
2
|
-
cd "$(dirname "$0")"
|
|
3
|
-
clear
|
|
2
|
+
cd "$(dirname "$0")"
|
|
4
3
|
echo ""
|
|
5
|
-
echo "
|
|
6
|
-
echo " Opening in your browser..."
|
|
4
|
+
echo " SEO Intel — Setup Wizard"
|
|
5
|
+
echo " Opening setup in your browser..."
|
|
7
6
|
echo ""
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
open "http://localhost:3000/setup" 2>/dev/null || xdg-open "http://localhost:3000/setup" 2>/dev/null
|
|
18
|
-
|
|
19
|
-
echo " Setup wizard is open at http://localhost:3000/setup"
|
|
20
|
-
echo " Keep this window open while using the wizard."
|
|
21
|
-
echo ""
|
|
22
|
-
read -n 1 -s -r -p " Press any key to stop the server and exit..."
|
|
23
|
-
|
|
24
|
-
# Clean up
|
|
25
|
-
if [ -n "$SERVER_PID" ]; then
|
|
26
|
-
kill $SERVER_PID 2>/dev/null
|
|
27
|
-
fi
|
|
7
|
+
node cli.js serve &
|
|
8
|
+
SERVER_PID=$!
|
|
9
|
+
# Wait for server to be ready
|
|
10
|
+
for i in {1..10}; do
|
|
11
|
+
sleep 1
|
|
12
|
+
if curl -s http://localhost:3000/ > /dev/null 2>&1; then break; fi
|
|
13
|
+
done
|
|
14
|
+
open "http://localhost:3000/setup"
|
|
15
|
+
wait $SERVER_PID
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "seo-intel",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.5",
|
|
4
4
|
"description": "Local Ahrefs-style SEO competitor intelligence. Crawl → SQLite → cloud analysis.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "SEE LICENSE IN LICENSE",
|
|
@@ -31,7 +31,15 @@
|
|
|
31
31
|
"scheduler.js",
|
|
32
32
|
"seo-audit.js",
|
|
33
33
|
"lib/",
|
|
34
|
-
"setup/",
|
|
34
|
+
"setup/checks.js",
|
|
35
|
+
"setup/config-builder.js",
|
|
36
|
+
"setup/engine.js",
|
|
37
|
+
"setup/installers.js",
|
|
38
|
+
"setup/models.js",
|
|
39
|
+
"setup/openclaw-bridge.js",
|
|
40
|
+
"setup/validator.js",
|
|
41
|
+
"setup/web-routes.js",
|
|
42
|
+
"setup/wizard.html",
|
|
35
43
|
"config/setup-wizard.js",
|
|
36
44
|
"config/example.json",
|
|
37
45
|
"crawler/",
|
package/reports/generate-html.js
CHANGED
|
@@ -2188,6 +2188,53 @@ function buildHtmlTemplate(data, opts = {}) {
|
|
|
2188
2188
|
if (scopeIdx > -1 && parts[scopeIdx + 1]) extra.scope = parts[scopeIdx + 1];
|
|
2189
2189
|
runCommand(cmd, proj, extra);
|
|
2190
2190
|
});
|
|
2191
|
+
|
|
2192
|
+
// Autorun: if URL has ?autorun=setup-classic, fire seo-intel setup --classic via SSE
|
|
2193
|
+
(function() {
|
|
2194
|
+
const urlParams = new URLSearchParams(window.location.search);
|
|
2195
|
+
if (urlParams.get('autorun') === 'setup-classic') {
|
|
2196
|
+
// Remove the param so it doesn't re-trigger on refresh
|
|
2197
|
+
window.history.replaceState({}, '', window.location.pathname);
|
|
2198
|
+
// Wait a tick for the panel to be ready, then stream the command
|
|
2199
|
+
setTimeout(function() {
|
|
2200
|
+
if (!isServed) {
|
|
2201
|
+
appendLine('Not connected to server. Cannot run setup --classic automatically.', 'error');
|
|
2202
|
+
return;
|
|
2203
|
+
}
|
|
2204
|
+
running = true;
|
|
2205
|
+
status.textContent = 'running...';
|
|
2206
|
+
status.style.color = 'var(--color-warning)';
|
|
2207
|
+
appendLine('$ seo-intel setup --classic', 'cmd');
|
|
2208
|
+
const params = new URLSearchParams({ command: 'setup', classic: 'true' });
|
|
2209
|
+
eventSource = new EventSource('/api/terminal?' + params.toString());
|
|
2210
|
+
eventSource.onmessage = function(e) {
|
|
2211
|
+
try {
|
|
2212
|
+
const msg = JSON.parse(e.data);
|
|
2213
|
+
if (msg.type === 'stdout') appendLine(msg.data, 'stdout');
|
|
2214
|
+
else if (msg.type === 'stderr') appendLine(msg.data, 'stderr');
|
|
2215
|
+
else if (msg.type === 'error') { appendLine('Error: ' + msg.data, 'error'); }
|
|
2216
|
+
else if (msg.type === 'exit') {
|
|
2217
|
+
const code = msg.data?.code ?? msg.data;
|
|
2218
|
+
appendLine(code === 0 ? 'Done.' : 'Exited with code ' + code, code === 0 ? 'exit-ok' : 'exit-err');
|
|
2219
|
+
running = false;
|
|
2220
|
+
status.textContent = code === 0 ? 'done' : 'failed';
|
|
2221
|
+
status.style.color = code === 0 ? 'var(--color-success)' : 'var(--color-danger)';
|
|
2222
|
+
eventSource.close();
|
|
2223
|
+
eventSource = null;
|
|
2224
|
+
}
|
|
2225
|
+
} catch (_) {}
|
|
2226
|
+
};
|
|
2227
|
+
eventSource.onerror = function() {
|
|
2228
|
+
if (running) { appendLine('Connection lost.', 'error'); }
|
|
2229
|
+
running = false;
|
|
2230
|
+
status.textContent = 'disconnected';
|
|
2231
|
+
status.style.color = 'var(--color-danger)';
|
|
2232
|
+
eventSource?.close();
|
|
2233
|
+
eventSource = null;
|
|
2234
|
+
};
|
|
2235
|
+
}, 300);
|
|
2236
|
+
}
|
|
2237
|
+
})();
|
|
2191
2238
|
})();
|
|
2192
2239
|
</script>
|
|
2193
2240
|
|
|
@@ -3499,7 +3546,6 @@ function buildHtmlTemplate(data, opts = {}) {
|
|
|
3499
3546
|
};
|
|
3500
3547
|
|
|
3501
3548
|
window.stopJob = function() {
|
|
3502
|
-
if (!confirm('Stop the running job?')) return;
|
|
3503
3549
|
fetch('/api/stop', { method: 'POST' })
|
|
3504
3550
|
.then(function(r) { return r.json(); })
|
|
3505
3551
|
.then(function(data) {
|
|
@@ -3582,6 +3628,11 @@ function buildHtmlTemplate(data, opts = {}) {
|
|
|
3582
3628
|
dot.className = 'es-dot';
|
|
3583
3629
|
label.style.color = 'var(--color-success)';
|
|
3584
3630
|
label.textContent = 'Completed (' + (data.extracted || 0) + ' extracted' + (data.failed ? ', ' + data.failed + ' failed' : '') + ')';
|
|
3631
|
+
} else if (data.status === 'stopped') {
|
|
3632
|
+
panel.classList.remove('is-running', 'is-crashed');
|
|
3633
|
+
dot.className = 'es-dot';
|
|
3634
|
+
label.style.color = 'var(--accent-gold)';
|
|
3635
|
+
label.textContent = 'Stopped' + (data.extracted ? ' (' + data.extracted + ' extracted)' : '');
|
|
3585
3636
|
} else if (data.status === 'crashed') {
|
|
3586
3637
|
panel.classList.remove('is-running');
|
|
3587
3638
|
panel.classList.add('is-crashed');
|
|
@@ -4691,7 +4742,6 @@ function buildMultiHtmlTemplate(allProjectData) {
|
|
|
4691
4742
|
};
|
|
4692
4743
|
|
|
4693
4744
|
window.stopJob = function() {
|
|
4694
|
-
if (!confirm('Stop the running job?')) return;
|
|
4695
4745
|
fetch('/api/stop', { method: 'POST' })
|
|
4696
4746
|
.then(function(r) { return r.json(); })
|
|
4697
4747
|
.then(function(data) {
|
|
@@ -4750,6 +4800,10 @@ function buildMultiHtmlTemplate(allProjectData) {
|
|
|
4750
4800
|
panel.classList.remove('is-running', 'is-crashed');
|
|
4751
4801
|
dot.className = 'es-dot'; label.style.color = 'var(--color-success)';
|
|
4752
4802
|
label.textContent = 'Completed (' + (data.extracted || 0) + ' extracted)';
|
|
4803
|
+
} else if (data.status === 'stopped') {
|
|
4804
|
+
panel.classList.remove('is-running', 'is-crashed');
|
|
4805
|
+
dot.className = 'es-dot'; label.style.color = 'var(--accent-gold)';
|
|
4806
|
+
label.textContent = 'Stopped' + (data.extracted ? ' (' + data.extracted + ' extracted)' : '');
|
|
4753
4807
|
} else if (data.status === 'crashed') {
|
|
4754
4808
|
panel.classList.remove('is-running'); panel.classList.add('is-crashed');
|
|
4755
4809
|
dot.className = 'es-dot crashed'; label.style.color = 'var(--color-danger)';
|
package/server.js
CHANGED
|
@@ -354,6 +354,15 @@ async function handleRequest(req, res) {
|
|
|
354
354
|
if (e.code !== 'ESRCH') throw e;
|
|
355
355
|
// Already dead
|
|
356
356
|
}
|
|
357
|
+
// Update progress file to reflect stopped state
|
|
358
|
+
try {
|
|
359
|
+
writeFileSync(PROGRESS_FILE, JSON.stringify({
|
|
360
|
+
...progress,
|
|
361
|
+
status: 'stopped',
|
|
362
|
+
stopped_at: Date.now(),
|
|
363
|
+
updated_at: Date.now(),
|
|
364
|
+
}, null, 2));
|
|
365
|
+
} catch { /* best-effort */ }
|
|
357
366
|
json(res, 200, { stopped: true, pid: progress.pid, command: progress.command });
|
|
358
367
|
} catch (e) {
|
|
359
368
|
json(res, 500, { error: e.message });
|
package/setup/openclaw-bridge.js
CHANGED
|
@@ -66,13 +66,39 @@ async function askAgent(messages, opts = {}) {
|
|
|
66
66
|
*/
|
|
67
67
|
export async function isGatewayReady() {
|
|
68
68
|
try {
|
|
69
|
+
// Try to read token from env or openclaw config file
|
|
70
|
+
let token = process.env.OPENCLAW_TOKEN;
|
|
71
|
+
if (!token) {
|
|
72
|
+
try {
|
|
73
|
+
const configPath = join(process.env.HOME || '~', '.openclaw', 'openclaw.json');
|
|
74
|
+
const { readFileSync } = await import('fs');
|
|
75
|
+
const raw = readFileSync(configPath, 'utf8');
|
|
76
|
+
// Gateway auth token is a 48-char hex string in the gateway.auth block
|
|
77
|
+
const matches = [...raw.matchAll(/"token":\s*"([a-f0-9]{40,})"/g)];
|
|
78
|
+
if (matches.length > 0) token = matches[matches.length - 1][1];
|
|
79
|
+
} catch {}
|
|
80
|
+
}
|
|
81
|
+
if (!token) return false;
|
|
82
|
+
|
|
83
|
+
// Verify gateway is reachable via a lightweight chat completions ping
|
|
69
84
|
const controller = new AbortController();
|
|
70
|
-
const timeout = setTimeout(() => controller.abort(),
|
|
71
|
-
const res = await fetch(`${OPENCLAW_API}/v1/
|
|
85
|
+
const timeout = setTimeout(() => controller.abort(), 5000);
|
|
86
|
+
const res = await fetch(`${OPENCLAW_API}/v1/chat/completions`, {
|
|
87
|
+
method: 'POST',
|
|
72
88
|
signal: controller.signal,
|
|
89
|
+
headers: {
|
|
90
|
+
'Authorization': `Bearer ${token}`,
|
|
91
|
+
'Content-Type': 'application/json',
|
|
92
|
+
},
|
|
93
|
+
body: JSON.stringify({
|
|
94
|
+
model: 'anthropic/claude-haiku-4-5',
|
|
95
|
+
messages: [{ role: 'user', content: 'ping' }],
|
|
96
|
+
max_tokens: 1,
|
|
97
|
+
}),
|
|
73
98
|
});
|
|
74
99
|
clearTimeout(timeout);
|
|
75
|
-
|
|
100
|
+
const ct = res.headers.get('content-type') || '';
|
|
101
|
+
return res.ok && ct.includes('application/json');
|
|
76
102
|
} catch {
|
|
77
103
|
return false;
|
|
78
104
|
}
|
package/setup/wizard.html
CHANGED
|
@@ -54,6 +54,7 @@ body {
|
|
|
54
54
|
max-width: var(--max-width);
|
|
55
55
|
margin: 0 auto 24px;
|
|
56
56
|
text-align: center;
|
|
57
|
+
position: relative;
|
|
57
58
|
}
|
|
58
59
|
.wizard-header h1 {
|
|
59
60
|
font-family: var(--font-display);
|
|
@@ -1172,6 +1173,7 @@ input::placeholder {
|
|
|
1172
1173
|
<div class="wizard-header">
|
|
1173
1174
|
<h1>SEO Intel</h1>
|
|
1174
1175
|
<div class="subtitle">Setup Wizard</div>
|
|
1176
|
+
<a href="http://localhost:3000/?autorun=setup-classic" title="Run setup in Terminal instead" style="position:absolute;top:14px;right:18px;font-size:0.68rem;color:var(--color-muted,#888);text-decoration:none;opacity:0.7;transition:opacity 0.15s;" onmouseover="this.style.opacity='1'" onmouseout="this.style.opacity='0.7'"><i class="fa-solid fa-terminal"></i> Terminal setup</a>
|
|
1175
1177
|
</div>
|
|
1176
1178
|
|
|
1177
1179
|
<!-- ═══════════════════════════════════════════════════════════════════════
|
package/setup/ROADMAP.md
DELETED
|
@@ -1,109 +0,0 @@
|
|
|
1
|
-
# SEO Intel Setup — Roadmap
|
|
2
|
-
|
|
3
|
-
> From open-source CLI tool → standalone product
|
|
4
|
-
|
|
5
|
-
## Current State (v0.2)
|
|
6
|
-
- [x] System detection (Node, npm, Ollama, Playwright, VRAM)
|
|
7
|
-
- [x] Model recommendations (VRAM-based extraction + analysis tiers)
|
|
8
|
-
- [x] Project configuration (target domain, competitors, crawl mode)
|
|
9
|
-
- [x] API key setup (Gemini, Claude, OpenAI, DeepSeek)
|
|
10
|
-
- [x] Pipeline validation (Ollama → API → crawl → extraction)
|
|
11
|
-
- [x] CLI wizard + Web wizard at /setup
|
|
12
|
-
- [x] GSC setup step (CSV upload + export guide + auto-detection)
|
|
13
|
-
- [x] License system (lib/license.js + lib/gate.js)
|
|
14
|
-
- [x] Free/Pro tier gating on all 23 CLI commands
|
|
15
|
-
- [x] Page limit enforcement (500/domain on free tier)
|
|
16
|
-
- [x] License status in `status` command
|
|
17
|
-
|
|
18
|
-
## Priority 1 — GSC Setup Guide
|
|
19
|
-
**Status: ✅ Done (CSV upload, export guide, auto-detection)**
|
|
20
|
-
|
|
21
|
-
Google Search Console is the #1 data source users need but can't figure out alone.
|
|
22
|
-
Currently: manual CSV export, no API, no guidance in wizard.
|
|
23
|
-
|
|
24
|
-
- [ ] Add Step 3.5: "Connect Google Search Console" in web wizard
|
|
25
|
-
- [ ] Visual walkthrough: how to export CSVs from GSC UI (screenshots/steps)
|
|
26
|
-
- [ ] Auto-detect existing GSC data in `gsc/` folder
|
|
27
|
-
- [ ] GSC API integration (service account JSON key upload)
|
|
28
|
-
- [ ] Auto-fetch GSC data on schedule (replaces manual CSV)
|
|
29
|
-
|
|
30
|
-
## Priority 2 — Ollama Auto-Install
|
|
31
|
-
**Status: 📋 Planned**
|
|
32
|
-
|
|
33
|
-
If Ollama isn't found, offer to install it instead of just warning.
|
|
34
|
-
|
|
35
|
-
- [ ] macOS: `brew install ollama` or direct download
|
|
36
|
-
- [ ] Linux: `curl -fsSL https://ollama.com/install.sh | sh`
|
|
37
|
-
- [ ] Windows: direct user to installer URL
|
|
38
|
-
- [ ] Auto-start Ollama after install
|
|
39
|
-
- [ ] Auto-pull recommended model after install
|
|
40
|
-
|
|
41
|
-
## Priority 3 — Scheduling / Automation
|
|
42
|
-
**Status: 📋 Planned**
|
|
43
|
-
|
|
44
|
-
After setup, users need recurring crawls. "Set and forget."
|
|
45
|
-
|
|
46
|
-
- [ ] "Schedule weekly crawl?" step in wizard
|
|
47
|
-
- [ ] Cron job generator (macOS launchd / Linux cron / Windows Task Scheduler)
|
|
48
|
-
- [ ] Built-in scheduler (node-cron or setTimeout loop in server.js)
|
|
49
|
-
- [ ] Crawl → Extract → Analyze → Regenerate dashboard pipeline
|
|
50
|
-
- [ ] "Last run" / "Next run" display on dashboard
|
|
51
|
-
|
|
52
|
-
## Priority 4 — First Run Experience
|
|
53
|
-
**Status: 📋 Planned**
|
|
54
|
-
|
|
55
|
-
Don't just show CLI commands — offer to run the first crawl right there.
|
|
56
|
-
|
|
57
|
-
- [ ] "Run your first crawl now?" button on Step 5
|
|
58
|
-
- [ ] SSE progress stream showing crawl progress in real-time
|
|
59
|
-
- [ ] Auto-trigger extraction + analysis after crawl
|
|
60
|
-
- [ ] Redirect to dashboard when done
|
|
61
|
-
- [ ] Estimated time based on competitor count × pages per domain
|
|
62
|
-
|
|
63
|
-
## Priority 5 — Proxy & Rate Limiting
|
|
64
|
-
**Status: 📋 Planned**
|
|
65
|
-
|
|
66
|
-
Stealth mode users need proxy config to avoid blocks.
|
|
67
|
-
|
|
68
|
-
- [ ] Proxy URL input (HTTP/SOCKS5)
|
|
69
|
-
- [ ] Proxy rotation list upload
|
|
70
|
-
- [ ] Rate limit slider (requests/minute)
|
|
71
|
-
- [ ] Per-domain delay configuration
|
|
72
|
-
- [ ] "Test proxy" validation step
|
|
73
|
-
|
|
74
|
-
## Priority 6 — Notifications
|
|
75
|
-
**Status: 📋 Planned**
|
|
76
|
-
|
|
77
|
-
Know when things happen without checking manually.
|
|
78
|
-
|
|
79
|
-
- [ ] Email notifications (SMTP setup in wizard)
|
|
80
|
-
- [ ] Slack webhook integration
|
|
81
|
-
- [ ] Discord webhook integration
|
|
82
|
-
- [ ] Configurable triggers: crawl complete, ranking drop, new competitor page
|
|
83
|
-
- [ ] Weekly digest email with key metrics
|
|
84
|
-
|
|
85
|
-
## Priority 7 — Data & Backup
|
|
86
|
-
**Status: 📋 Planned**
|
|
87
|
-
|
|
88
|
-
Where data lives, how big it gets, how to manage it.
|
|
89
|
-
|
|
90
|
-
- [ ] Show data directory + size in dashboard footer
|
|
91
|
-
- [ ] One-click export (SQLite → JSON/CSV)
|
|
92
|
-
- [ ] Auto-backup before major operations
|
|
93
|
-
- [ ] Data retention settings (keep last N crawls)
|
|
94
|
-
- [ ] Cloud backup option (S3/GCS)
|
|
95
|
-
|
|
96
|
-
---
|
|
97
|
-
|
|
98
|
-
## Open Source → Product Progression
|
|
99
|
-
|
|
100
|
-
| Feature | Open Source (froggo.pro) | Standalone SaaS |
|
|
101
|
-
|---------|------------------------|-----------------|
|
|
102
|
-
| Setup | CLI wizard | Web wizard + onboarding email |
|
|
103
|
-
| Auth | None (local) | User accounts + API keys |
|
|
104
|
-
| GSC | Manual CSV or API key | OAuth "Connect GSC" button |
|
|
105
|
-
| Scheduling | Cron jobs | Built-in + hosted workers |
|
|
106
|
-
| Notifications | Webhook only | Email + Slack + in-app |
|
|
107
|
-
| Data | Local SQLite | Cloud DB + CDN dashboards |
|
|
108
|
-
| Multi-user | Single | Teams + permissions |
|
|
109
|
-
| Billing | Free / one-time | Subscription tiers |
|