seo-intel 1.4.0 → 1.4.2
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 +39 -0
- package/analyses/watch/diff.js +158 -0
- package/analyses/watch/health.js +78 -0
- package/analyses/watch/index.js +215 -0
- package/cli.js +407 -111
- package/db/db.js +73 -0
- package/package.json +17 -1
- package/reports/generate-html.js +150 -2
- package/reports/gsc-loader.js +14 -4
- package/server.js +1 -1
- package/setup/checks.js +9 -2
- package/setup/web-routes.js +26 -1
- package/setup/wizard.html +480 -323
package/setup/wizard.html
CHANGED
|
@@ -340,6 +340,26 @@ body {
|
|
|
340
340
|
grid-template-columns: 1fr 1fr 1fr;
|
|
341
341
|
gap: 20px;
|
|
342
342
|
}
|
|
343
|
+
/* Floating genie card — follows scroll on the right, never takes layout space */
|
|
344
|
+
.genie-card {
|
|
345
|
+
position: fixed;
|
|
346
|
+
top: 80px;
|
|
347
|
+
right: 24px;
|
|
348
|
+
width: 280px;
|
|
349
|
+
z-index: 50;
|
|
350
|
+
box-shadow: 0 8px 40px rgba(0,0,0,0.5);
|
|
351
|
+
border-color: rgba(124,109,235,0.3);
|
|
352
|
+
max-height: calc(100vh - 100px);
|
|
353
|
+
overflow-y: auto;
|
|
354
|
+
transition: opacity 0.2s;
|
|
355
|
+
}
|
|
356
|
+
.genie-card::-webkit-scrollbar { width: 3px; }
|
|
357
|
+
.genie-card::-webkit-scrollbar-thumb { background: rgba(124,109,235,0.3); border-radius: 3px; }
|
|
358
|
+
.genie-card .genie-close {
|
|
359
|
+
position: absolute; top: 10px; right: 10px;
|
|
360
|
+
background: none; border: none; color: var(--text-muted); cursor: pointer; font-size: 0.75rem;
|
|
361
|
+
}
|
|
362
|
+
.genie-card .genie-close:hover { color: var(--text-primary); }
|
|
343
363
|
.model-section {
|
|
344
364
|
position: relative;
|
|
345
365
|
}
|
|
@@ -1159,6 +1179,7 @@ input::placeholder {
|
|
|
1159
1179
|
body { padding: 16px 12px; }
|
|
1160
1180
|
.dep-grid { grid-template-columns: 1fr; }
|
|
1161
1181
|
.model-columns { grid-template-columns: 1fr; }
|
|
1182
|
+
.genie-card { display: none; }
|
|
1162
1183
|
.form-row { grid-template-columns: 1fr; }
|
|
1163
1184
|
.step-indicator { flex-wrap: nowrap; gap: 0; overflow-x: auto; }
|
|
1164
1185
|
.step-connector { width: 12px; min-width: 12px; }
|
|
@@ -1288,139 +1309,6 @@ input::placeholder {
|
|
|
1288
1309
|
<!-- Populated by JS -->
|
|
1289
1310
|
</div>
|
|
1290
1311
|
|
|
1291
|
-
<!-- OpenClaw Setup Banner -->
|
|
1292
|
-
<div id="openclawBanner" class="openclaw-banner" style="display:none;">
|
|
1293
|
-
<!-- Always-shown agentic setup block -->
|
|
1294
|
-
<div id="ocBannerRecommended" style="display:none;">
|
|
1295
|
-
<div class="openclaw-banner-title" style="margin-bottom:8px;">
|
|
1296
|
-
<i class="fa-solid fa-wand-magic-sparkles" style="color:var(--accent-purple); margin-right:6px;"></i>
|
|
1297
|
-
Agentic Setup — let an AI handle this
|
|
1298
|
-
</div>
|
|
1299
|
-
<div class="openclaw-banner-desc" style="margin-bottom:14px;">Pick your agent. Copy the prompt. Paste it in. Done.</div>
|
|
1300
|
-
|
|
1301
|
-
<!-- Runtime tabs -->
|
|
1302
|
-
<div style="display:flex; gap:6px; flex-wrap:wrap; margin-bottom:12px;" id="agentRuntimeTabs">
|
|
1303
|
-
<button class="btn btn-sm agent-runtime-tab active" data-runtime="openclaw" onclick="selectAgentRuntime('openclaw', this)" style="font-size:0.65rem;">
|
|
1304
|
-
<i class="fa-solid fa-wand-magic-sparkles" style="margin-right:4px;"></i>OpenClaw
|
|
1305
|
-
</button>
|
|
1306
|
-
<button class="btn btn-sm agent-runtime-tab" data-runtime="claudecode" onclick="selectAgentRuntime('claudecode', this)" style="font-size:0.65rem;">
|
|
1307
|
-
<i class="fa-solid fa-terminal" style="margin-right:4px;"></i>Claude Code
|
|
1308
|
-
</button>
|
|
1309
|
-
<button class="btn btn-sm agent-runtime-tab" data-runtime="codex" onclick="selectAgentRuntime('codex', this)" style="font-size:0.65rem;">
|
|
1310
|
-
<i class="fa-solid fa-code" style="margin-right:4px;"></i>Codex CLI
|
|
1311
|
-
</button>
|
|
1312
|
-
<button class="btn btn-sm agent-runtime-tab" data-runtime="perplexity" onclick="selectAgentRuntime('perplexity', this)" style="font-size:0.65rem;">
|
|
1313
|
-
<i class="fa-solid fa-magnifying-glass" style="margin-right:4px;"></i>Perplexity
|
|
1314
|
-
</button>
|
|
1315
|
-
</div>
|
|
1316
|
-
|
|
1317
|
-
<!-- Runtime panels -->
|
|
1318
|
-
<div id="agentRuntime-openclaw" class="agent-runtime-panel">
|
|
1319
|
-
<div style="font-size:0.68rem; color:var(--text-muted); margin-bottom:8px;">OpenClaw is running — it can guide setup directly. Run this in your terminal first:</div>
|
|
1320
|
-
<div style="padding:8px 10px; background:rgba(10,10,10,0.5); border:1px solid var(--border-subtle); border-radius:var(--radius); font-family:var(--font-mono); font-size:0.68rem; color:var(--text-secondary); display:flex; align-items:center; gap:8px; margin-bottom:8px;">
|
|
1321
|
-
<span style="color:var(--text-muted);">$</span>
|
|
1322
|
-
<span id="clawhubCmd">clawhub install ukkometa/seo-intel</span>
|
|
1323
|
-
<button class="btn btn-sm" style="margin-left:auto; padding:3px 8px; font-size:0.6rem;" onclick="navigator.clipboard.writeText('clawhub install ukkometa/seo-intel');this.textContent='Copied!';setTimeout(()=>this.textContent='Copy',1500);">Copy</button>
|
|
1324
|
-
</div>
|
|
1325
|
-
<div style="font-size:0.65rem; color:var(--text-muted); margin-bottom:8px;">Or paste this into your OpenClaw chat:</div>
|
|
1326
|
-
<div style="position:relative; padding:8px 10px; background:rgba(10,10,10,0.5); border:1px solid var(--border-subtle); border-radius:var(--radius); font-family:var(--font-mono); font-size:0.62rem; color:var(--text-secondary); line-height:1.6; margin-bottom:10px;">
|
|
1327
|
-
<div id="oc-prompt">Set up SEO Intel on my machine.
|
|
1328
|
-
Check dependencies, install Ollama if needed, recommend a model based on my VRAM, configure a project with my target site, and run a test crawl.</div>
|
|
1329
|
-
<button class="btn btn-sm" style="position:absolute; top:6px; right:6px; padding:2px 6px; font-size:0.5rem;" onclick="navigator.clipboard.writeText(document.getElementById('oc-prompt').textContent.trim());this.textContent='Copied!';setTimeout(()=>this.textContent='Copy',1500);">Copy</button>
|
|
1330
|
-
</div>
|
|
1331
|
-
<div class="openclaw-banner-actions">
|
|
1332
|
-
<button class="btn btn-gold" onclick="startAgentSetup()"><i class="fa-solid fa-play"></i> Start Agent Setup</button>
|
|
1333
|
-
<button class="btn" onclick="continueManualSetup()"><i class="fa-solid fa-list-check"></i> Manual</button>
|
|
1334
|
-
</div>
|
|
1335
|
-
</div>
|
|
1336
|
-
|
|
1337
|
-
<div id="agentRuntime-claudecode" class="agent-runtime-panel" style="display:none;">
|
|
1338
|
-
<div style="font-size:0.68rem; color:var(--text-muted); margin-bottom:8px;">Open Claude Code in your terminal (<code style="background:rgba(255,255,255,0.05);padding:1px 4px;border-radius:3px;">claude</code>) and paste this prompt:</div>
|
|
1339
|
-
<div style="position:relative; padding:8px 10px; background:rgba(10,10,10,0.5); border:1px solid var(--border-subtle); border-radius:var(--radius); font-family:var(--font-mono); font-size:0.62rem; color:var(--text-secondary); line-height:1.6; margin-bottom:8px;">
|
|
1340
|
-
<div id="cc-prompt">I'm setting up SEO Intel (npm package: seo-intel).
|
|
1341
|
-
Help me:
|
|
1342
|
-
1. Check Node.js 22.5+ is installed
|
|
1343
|
-
2. Install Ollama if missing (https://ollama.com)
|
|
1344
|
-
3. Pull a Qwen model matching my VRAM (run: system_profiler SPDisplaysDataType | grep VRAM)
|
|
1345
|
-
4. Create a config file at ./config/myproject.json with my target site
|
|
1346
|
-
5. Run: seo-intel crawl myproject
|
|
1347
|
-
6. Fix any errors that come up
|
|
1348
|
-
|
|
1349
|
-
Start by checking what's already installed.</div>
|
|
1350
|
-
<button class="btn btn-sm" style="position:absolute; top:6px; right:6px; padding:2px 6px; font-size:0.5rem;" onclick="navigator.clipboard.writeText(document.getElementById('cc-prompt').textContent.trim());this.textContent='Copied!';setTimeout(()=>this.textContent='Copy',1500);">Copy</button>
|
|
1351
|
-
</div>
|
|
1352
|
-
<div style="font-size:0.62rem; color:var(--text-muted);">Run Claude Code with: <code style="background:rgba(255,255,255,0.05);padding:1px 4px;border-radius:3px;">claude --permission-mode bypassPermissions</code> for full access</div>
|
|
1353
|
-
</div>
|
|
1354
|
-
|
|
1355
|
-
<div id="agentRuntime-codex" class="agent-runtime-panel" style="display:none;">
|
|
1356
|
-
<div style="font-size:0.68rem; color:var(--text-muted); margin-bottom:8px;">Open Codex CLI (<code style="background:rgba(255,255,255,0.05);padding:1px 4px;border-radius:3px;">codex</code>) and paste this prompt:</div>
|
|
1357
|
-
<div style="position:relative; padding:8px 10px; background:rgba(10,10,10,0.5); border:1px solid var(--border-subtle); border-radius:var(--radius); font-family:var(--font-mono); font-size:0.62rem; color:var(--text-secondary); line-height:1.6; margin-bottom:8px;">
|
|
1358
|
-
<div id="cx-prompt">Set up the seo-intel npm CLI tool on this machine.
|
|
1359
|
-
Tasks:
|
|
1360
|
-
1. Verify Node.js 22.5+ (install via nvm if needed)
|
|
1361
|
-
2. Install Ollama (https://ollama.com) if not present
|
|
1362
|
-
3. Pull gemma4:e4b or smaller if VRAM < 6GB
|
|
1363
|
-
4. Create ./config/myproject.json — ask me for my target domain and up to 3 competitor domains
|
|
1364
|
-
5. Add GEMINI_API_KEY or ANTHROPIC_API_KEY to .env if I have one
|
|
1365
|
-
6. Run: seo-intel crawl myproject
|
|
1366
|
-
7. Fix any errors
|
|
1367
|
-
|
|
1368
|
-
Use full disk access. Check what's installed before proceeding.</div>
|
|
1369
|
-
<button class="btn btn-sm" style="position:absolute; top:6px; right:6px; padding:2px 6px; font-size:0.5rem;" onclick="navigator.clipboard.writeText(document.getElementById('cx-prompt').textContent.trim());this.textContent='Copied!';setTimeout(()=>this.textContent='Copy',1500);">Copy</button>
|
|
1370
|
-
</div>
|
|
1371
|
-
<div style="font-size:0.62rem; color:var(--text-muted);">Codex config: <code style="background:rgba(255,255,255,0.05);padding:1px 4px;border-radius:3px;">sandbox_mode = "danger-full-access"</code> recommended for install tasks</div>
|
|
1372
|
-
</div>
|
|
1373
|
-
|
|
1374
|
-
<div id="agentRuntime-perplexity" class="agent-runtime-panel" style="display:none;">
|
|
1375
|
-
<div style="font-size:0.68rem; color:var(--text-muted); margin-bottom:8px;">Open Perplexity Computer and paste this prompt:</div>
|
|
1376
|
-
<div style="position:relative; padding:8px 10px; background:rgba(10,10,10,0.5); border:1px solid var(--border-subtle); border-radius:var(--radius); font-family:var(--font-mono); font-size:0.62rem; color:var(--text-secondary); line-height:1.6; margin-bottom:8px;">
|
|
1377
|
-
<div id="pp-prompt">I want to set up a local SEO analysis tool called SEO Intel.
|
|
1378
|
-
It's installed via: npm install -g seo-intel
|
|
1379
|
-
|
|
1380
|
-
Please help me:
|
|
1381
|
-
1. Check that Node.js 22.5+ is installed on my machine
|
|
1382
|
-
2. Install Ollama (ollama.com) if it's not there
|
|
1383
|
-
3. Download a Gemma 4 AI model that fits my machine's RAM
|
|
1384
|
-
4. Create a project config file pointing at my website and competitors
|
|
1385
|
-
5. Run the first crawl and show me the results
|
|
1386
|
-
|
|
1387
|
-
Ask me for my website URL before starting.</div>
|
|
1388
|
-
<button class="btn btn-sm" style="position:absolute; top:6px; right:6px; padding:2px 6px; font-size:0.5rem;" onclick="navigator.clipboard.writeText(document.getElementById('pp-prompt').textContent.trim());this.textContent='Copied!';setTimeout(()=>this.textContent='Copy',1500);">Copy</button>
|
|
1389
|
-
</div>
|
|
1390
|
-
<div style="font-size:0.62rem; color:var(--text-muted);">Works best with Perplexity Computer's full computer-use mode enabled</div>
|
|
1391
|
-
</div>
|
|
1392
|
-
</div>
|
|
1393
|
-
|
|
1394
|
-
<div id="ocBannerMinimal" style="display:none;">
|
|
1395
|
-
<div class="openclaw-banner-desc" style="color:var(--text-muted); font-size:0.72rem; margin-bottom:10px;">
|
|
1396
|
-
<strong style="color:var(--text-secondary);">Agent setup available.</strong> Pick a runtime below and copy the prompt:
|
|
1397
|
-
</div>
|
|
1398
|
-
<!-- Same runtime tabs, minimal version -->
|
|
1399
|
-
<div style="display:flex; gap:6px; flex-wrap:wrap; margin-bottom:10px;">
|
|
1400
|
-
<button class="btn btn-sm agent-runtime-tab-min active" data-runtime="claudecode" onclick="selectAgentRuntimeMin('claudecode', this)" style="font-size:0.62rem;"><i class="fa-solid fa-terminal" style="margin-right:3px;"></i>Claude Code</button>
|
|
1401
|
-
<button class="btn btn-sm agent-runtime-tab-min" data-runtime="codex" onclick="selectAgentRuntimeMin('codex', this)" style="font-size:0.62rem;"><i class="fa-solid fa-code" style="margin-right:3px;"></i>Codex CLI</button>
|
|
1402
|
-
<button class="btn btn-sm agent-runtime-tab-min" data-runtime="perplexity" onclick="selectAgentRuntimeMin('perplexity', this)" style="font-size:0.62rem;"><i class="fa-solid fa-magnifying-glass" style="margin-right:3px;"></i>Perplexity</button>
|
|
1403
|
-
<button class="btn btn-sm agent-runtime-tab-min" data-runtime="openclaw" onclick="selectAgentRuntimeMin('openclaw', this)" style="font-size:0.62rem;"><i class="fa-solid fa-wand-magic-sparkles" style="margin-right:3px;"></i>OpenClaw</button>
|
|
1404
|
-
</div>
|
|
1405
|
-
<div id="minRuntime-claudecode" class="min-runtime-panel" style="position:relative; padding:7px 9px; background:rgba(10,10,10,0.5); border:1px solid var(--border-subtle); border-radius:var(--radius); font-family:var(--font-mono); font-size:0.6rem; color:var(--text-secondary); line-height:1.5;">
|
|
1406
|
-
<div>Set up seo-intel: check Node 22.5+, install Ollama, pull gemma4:e4b, create config, run crawl.</div>
|
|
1407
|
-
<button class="btn btn-sm" style="position:absolute; top:4px; right:4px; padding:2px 5px; font-size:0.48rem;" onclick="navigator.clipboard.writeText(this.previousElementSibling.textContent.trim());this.textContent='Copied!';setTimeout(()=>this.textContent='Copy',1500);">Copy</button>
|
|
1408
|
-
</div>
|
|
1409
|
-
<div id="minRuntime-codex" class="min-runtime-panel" style="display:none; position:relative; padding:7px 9px; background:rgba(10,10,10,0.5); border:1px solid var(--border-subtle); border-radius:var(--radius); font-family:var(--font-mono); font-size:0.6rem; color:var(--text-secondary); line-height:1.5;">
|
|
1410
|
-
<div>Install seo-intel deps (Node 22.5+, Ollama, gemma4:e4b), create project config, run: seo-intel crawl myproject. Use full disk access.</div>
|
|
1411
|
-
<button class="btn btn-sm" style="position:absolute; top:4px; right:4px; padding:2px 5px; font-size:0.48rem;" onclick="navigator.clipboard.writeText(this.previousElementSibling.textContent.trim());this.textContent='Copied!';setTimeout(()=>this.textContent='Copy',1500);">Copy</button>
|
|
1412
|
-
</div>
|
|
1413
|
-
<div id="minRuntime-perplexity" class="min-runtime-panel" style="display:none; position:relative; padding:7px 9px; background:rgba(10,10,10,0.5); border:1px solid var(--border-subtle); border-radius:var(--radius); font-family:var(--font-mono); font-size:0.6rem; color:var(--text-secondary); line-height:1.5;">
|
|
1414
|
-
<div>Help me set up SEO Intel local SEO tool: install Node.js 22.5+, Ollama, download Gemma 4 model, create config for my website. Ask for my URL.</div>
|
|
1415
|
-
<button class="btn btn-sm" style="position:absolute; top:4px; right:4px; padding:2px 5px; font-size:0.48rem;" onclick="navigator.clipboard.writeText(this.previousElementSibling.textContent.trim());this.textContent='Copied!';setTimeout(()=>this.textContent='Copy',1500);">Copy</button>
|
|
1416
|
-
</div>
|
|
1417
|
-
<div id="minRuntime-openclaw" class="min-runtime-panel" style="display:none; position:relative; padding:7px 9px; background:rgba(10,10,10,0.5); border:1px solid var(--border-subtle); border-radius:var(--radius); font-family:var(--font-mono); font-size:0.6rem; color:var(--text-secondary); line-height:1.5;">
|
|
1418
|
-
<div>clawhub install ukkometa/seo-intel</div>
|
|
1419
|
-
<button class="btn btn-sm" style="position:absolute; top:4px; right:4px; padding:2px 5px; font-size:0.48rem;" onclick="navigator.clipboard.writeText('clawhub install ukkometa/seo-intel');this.textContent='Copied!';setTimeout(()=>this.textContent='Copy',1500);">Copy</button>
|
|
1420
|
-
</div>
|
|
1421
|
-
</div>
|
|
1422
|
-
</div>
|
|
1423
|
-
|
|
1424
1312
|
<div id="installLog" class="log-output"></div>
|
|
1425
1313
|
<div style="display:flex; gap:10px; justify-content: flex-end; margin-top: 8px;">
|
|
1426
1314
|
<button class="btn" onclick="runSystemCheck()"><i class="fa-solid fa-arrows-rotate"></i> Re-check</button>
|
|
@@ -1439,11 +1327,15 @@ Ask me for my website URL before starting.</div>
|
|
|
1439
1327
|
<div class="step-panel" id="step2" style="max-width:none;">
|
|
1440
1328
|
<div style="position:relative;">
|
|
1441
1329
|
<div class="card" style="margin-bottom:0; min-width:0;">
|
|
1442
|
-
<div style="display:flex; align-items:
|
|
1443
|
-
<
|
|
1444
|
-
|
|
1445
|
-
<
|
|
1446
|
-
</
|
|
1330
|
+
<div style="display:flex; flex-wrap:wrap; gap:12px; align-items:flex-start; justify-content:space-between; margin-bottom:16px;">
|
|
1331
|
+
<div>
|
|
1332
|
+
<h2 style="margin-bottom:6px;"><i class="fa-solid fa-microchip"></i> Models & Auth</h2>
|
|
1333
|
+
<div class="hint" style="margin-bottom:0;">Choose your local extraction engine and your analysis provider, then save them without rerunning the whole setup.</div>
|
|
1334
|
+
</div>
|
|
1335
|
+
<div style="display:flex; gap:8px; flex-wrap:wrap; align-items:center; justify-content:flex-end;">
|
|
1336
|
+
<button class="btn" onclick="restartDashboard()"><i class="fa-solid fa-rotate-right"></i> Restart Dashboard</button>
|
|
1337
|
+
<button class="btn btn-gold" onclick="saveModelsModule()"><i class="fa-solid fa-floppy-disk"></i> Save Models/Auth</button>
|
|
1338
|
+
</div>
|
|
1447
1339
|
</div>
|
|
1448
1340
|
|
|
1449
1341
|
<!-- Ollama Hosts -->
|
|
@@ -1625,44 +1517,8 @@ Ask me for my website URL before starting.</div>
|
|
|
1625
1517
|
</div>
|
|
1626
1518
|
</div>
|
|
1627
1519
|
|
|
1628
|
-
|
|
1629
|
-
<p style="font-size:0.72rem; color:var(--text-muted); line-height:1.6; margin-bottom:12px;">
|
|
1630
|
-
Route analysis through frontier models. OpenClaw manages API keys, OAuth, and model routing automatically.
|
|
1631
|
-
</p>
|
|
1632
|
-
|
|
1633
|
-
<div style="font-size:0.68rem; color:var(--text-secondary); margin-bottom:12px;">
|
|
1634
|
-
<div style="display:flex; align-items:center; gap:6px; margin-bottom:6px;">
|
|
1635
|
-
<i class="fa-solid fa-check" style="color:var(--accent-purple); font-size:0.6rem; width:12px;"></i>
|
|
1636
|
-
Claude Opus 4.6
|
|
1637
|
-
</div>
|
|
1638
|
-
<div style="display:flex; align-items:center; gap:6px; margin-bottom:6px;">
|
|
1639
|
-
<i class="fa-solid fa-check" style="color:var(--accent-purple); font-size:0.6rem; width:12px;"></i>
|
|
1640
|
-
Gemini 3.1 Pro
|
|
1641
|
-
</div>
|
|
1642
|
-
<div style="display:flex; align-items:center; gap:6px; margin-bottom:6px;">
|
|
1643
|
-
<i class="fa-solid fa-check" style="color:var(--accent-purple); font-size:0.6rem; width:12px;"></i>
|
|
1644
|
-
GPT-5.4
|
|
1645
|
-
</div>
|
|
1646
|
-
</div>
|
|
1647
|
-
|
|
1648
|
-
<p style="font-size:0.62rem; color:var(--text-muted); line-height:1.5; margin-bottom:8px;">
|
|
1649
|
-
No .env keys needed. OAuth-based model access means the pipeline test may show "no API key" — this is normal when using OpenClaw.
|
|
1650
|
-
</p>
|
|
1651
|
-
|
|
1652
|
-
<div style="padding:6px 8px; background:rgba(10,10,10,0.5); border:1px solid var(--border-subtle); border-radius:var(--radius); font-family:var(--font-mono); font-size:0.65rem; color:var(--text-secondary); display:flex; align-items:center; gap:6px; margin-bottom:8px;">
|
|
1653
|
-
<span style="color:var(--text-muted);">$</span>
|
|
1654
|
-
<span>clawhub install ukkometa/seo-intel</span>
|
|
1655
|
-
<button class="btn btn-sm" style="margin-left:auto; padding:2px 6px; font-size:0.55rem;" onclick="navigator.clipboard.writeText('clawhub install ukkometa/seo-intel');this.textContent='Copied!';setTimeout(()=>this.textContent='Copy',1500);">Copy</button>
|
|
1656
|
-
</div>
|
|
1657
|
-
|
|
1658
|
-
<p style="font-size:0.6rem; color:var(--text-muted); margin-bottom:6px;">Or paste into OpenClaw chat:</p>
|
|
1659
|
-
<div style="padding:8px 10px; background:rgba(10,10,10,0.5); border:1px solid var(--border-subtle); border-radius:var(--radius); font-family:var(--font-mono); font-size:0.6rem; color:var(--text-secondary); line-height:1.6; position:relative;">
|
|
1660
|
-
<div style="white-space:pre;">Set up SEO Intel cloud analysis.
|
|
1661
|
-
Use Gemini for extraction, Claude for analysis.
|
|
1662
|
-
Configure API keys and run a test crawl.</div>
|
|
1663
|
-
<button class="btn btn-sm" style="position:absolute; top:6px; right:6px; padding:2px 6px; font-size:0.5rem;" onclick="navigator.clipboard.writeText('Set up SEO Intel cloud analysis.\nUse Gemini for extraction, Claude for analysis.\nConfigure API keys and run a test crawl.');this.textContent='Copied!';setTimeout(()=>this.textContent='Copy',1500);">Copy</button>
|
|
1664
|
-
</div>
|
|
1665
1520
|
</div>
|
|
1521
|
+
<div id="modelsModuleStatus" style="margin-top:10px; font-size:0.68rem; color:var(--text-muted);"></div>
|
|
1666
1522
|
<!-- Unified upgrade card (free tier only) -->
|
|
1667
1523
|
<div id="unifiedUpgrade" style="display:none; position:relative; margin-top:0; padding:48px 32px; border:1px solid rgba(232,213,163,0.15); border-radius:var(--radius); overflow:hidden; text-align:center;">
|
|
1668
1524
|
<div class="upgrade-overlay-bg" style="position:absolute;inset:0;"></div>
|
|
@@ -1694,68 +1550,29 @@ Configure API keys and run a test crawl.</div>
|
|
|
1694
1550
|
</div>
|
|
1695
1551
|
</div>
|
|
1696
1552
|
|
|
1697
|
-
|
|
1698
|
-
<div class="card" id="openclawFloating" style="margin-bottom:0; position:fixed; top:80px; right:20px; width:240px; z-index:50; box-shadow:0 8px 40px rgba(0,0,0,0.5); border-color:rgba(124,109,235,0.3);">
|
|
1699
|
-
<button onclick="document.getElementById('openclawFloating').style.display='none'" style="position:absolute;top:8px;right:8px;background:none;border:none;color:var(--text-muted);cursor:pointer;font-size:0.7rem;"><i class="fa-solid fa-xmark"></i></button>
|
|
1700
|
-
<div style="display:flex; align-items:center; gap:10px; margin-bottom:12px;">
|
|
1701
|
-
<svg width="22" height="22" viewBox="0 0 28 28" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
1702
|
-
<circle cx="14" cy="14" r="13" stroke="var(--accent-purple)" stroke-width="1.5" opacity="0.6"/>
|
|
1703
|
-
<path d="M9 12c0-2.8 2.2-5 5-5s5 2.2 5 5c0 1.8-1 3.4-2.4 4.3L18 20H10l1.4-3.7C10 15.4 9 13.8 9 12z" fill="var(--accent-purple)" opacity="0.8"/>
|
|
1704
|
-
</svg>
|
|
1705
|
-
<h3 style="font-size:0.85rem; font-weight:600; color:var(--text-primary); margin:0;">OpenClaw</h3>
|
|
1706
|
-
</div>
|
|
1707
|
-
|
|
1708
|
-
<p style="font-size:0.72rem; color:var(--text-muted); line-height:1.6; margin-bottom:12px;">
|
|
1709
|
-
Route analysis through frontier models. OpenClaw manages API keys, OAuth, and model routing automatically.
|
|
1710
|
-
</p>
|
|
1711
|
-
|
|
1712
|
-
<div style="font-size:0.68rem; color:var(--text-secondary); margin-bottom:12px;">
|
|
1713
|
-
<div style="display:flex; align-items:center; gap:6px; margin-bottom:6px;">
|
|
1714
|
-
<i class="fa-solid fa-check" style="color:var(--accent-purple); font-size:0.6rem; width:12px;"></i>
|
|
1715
|
-
Claude Opus 4.6
|
|
1716
|
-
</div>
|
|
1717
|
-
<div style="display:flex; align-items:center; gap:6px; margin-bottom:6px;">
|
|
1718
|
-
<i class="fa-solid fa-check" style="color:var(--accent-purple); font-size:0.6rem; width:12px;"></i>
|
|
1719
|
-
Gemini 3.1 Pro
|
|
1720
|
-
</div>
|
|
1721
|
-
<div style="display:flex; align-items:center; gap:6px; margin-bottom:6px;">
|
|
1722
|
-
<i class="fa-solid fa-check" style="color:var(--accent-purple); font-size:0.6rem; width:12px;"></i>
|
|
1723
|
-
GPT-5.4
|
|
1724
|
-
</div>
|
|
1725
|
-
</div>
|
|
1726
|
-
|
|
1727
|
-
<p style="font-size:0.62rem; color:var(--text-muted); line-height:1.5; margin-bottom:8px;">
|
|
1728
|
-
No .env keys needed. OAuth-based model access means the pipeline test may show "no API key" — this is normal when using OpenClaw.
|
|
1729
|
-
</p>
|
|
1730
|
-
|
|
1731
|
-
<div style="padding:6px 8px; background:rgba(10,10,10,0.5); border:1px solid var(--border-subtle); border-radius:var(--radius); font-family:var(--font-mono); font-size:0.65rem; color:var(--text-secondary); display:flex; align-items:center; gap:6px; margin-bottom:8px;">
|
|
1732
|
-
<span style="color:var(--text-muted);">$</span>
|
|
1733
|
-
<span>clawhub install ukkometa/seo-intel</span>
|
|
1734
|
-
<button class="btn btn-sm" style="margin-left:auto; padding:2px 6px; font-size:0.55rem;" onclick="navigator.clipboard.writeText('clawhub install ukkometa/seo-intel');this.textContent='Copied!';setTimeout(()=>this.textContent='Copy',1500);">Copy</button>
|
|
1735
|
-
</div>
|
|
1736
|
-
|
|
1737
|
-
<p style="font-size:0.6rem; color:var(--text-muted); margin-bottom:6px;">Or paste into OpenClaw chat:</p>
|
|
1738
|
-
<div style="padding:8px 10px; background:rgba(10,10,10,0.5); border:1px solid var(--border-subtle); border-radius:var(--radius); font-family:var(--font-mono); font-size:0.6rem; color:var(--text-secondary); line-height:1.6; position:relative;">
|
|
1739
|
-
<div style="white-space:pre;">Set up SEO Intel cloud analysis.
|
|
1740
|
-
Use Gemini for extraction, Claude for analysis.
|
|
1741
|
-
Configure API keys and run a test crawl.</div>
|
|
1742
|
-
<button class="btn btn-sm" style="position:absolute; top:6px; right:6px; padding:2px 6px; font-size:0.5rem;" onclick="navigator.clipboard.writeText('Set up SEO Intel cloud analysis.\nUse Gemini for extraction, Claude for analysis.\nConfigure API keys and run a test crawl.');this.textContent='Copied!';setTimeout(()=>this.textContent='Copy',1500);">Copy</button>
|
|
1743
|
-
</div>
|
|
1744
|
-
</div>
|
|
1745
|
-
|
|
1746
|
-
</div><!-- close step2 outer grid -->
|
|
1553
|
+
</div><!-- close step2 position:relative -->
|
|
1747
1554
|
</div>
|
|
1748
1555
|
|
|
1749
1556
|
<!-- ─── Step 3: Project Configuration ─────────────────────────────────── -->
|
|
1750
1557
|
<div class="step-panel" id="step3">
|
|
1751
1558
|
<div class="card">
|
|
1752
|
-
<
|
|
1559
|
+
<div style="display:flex; flex-wrap:wrap; gap:12px; align-items:flex-start; justify-content:space-between; margin-bottom:16px;">
|
|
1560
|
+
<div>
|
|
1561
|
+
<h2 style="margin-bottom:6px;"><i class="fa-solid fa-folder-open"></i> Project Configuration</h2>
|
|
1562
|
+
<div style="font-size:0.68rem; color:var(--text-muted);">Edit project settings without rerunning the whole setup flow.</div>
|
|
1563
|
+
</div>
|
|
1564
|
+
<div style="display:flex; gap:8px; flex-wrap:wrap; align-items:center; justify-content:flex-end;">
|
|
1565
|
+
<button class="btn" onclick="restartDashboard()"><i class="fa-solid fa-rotate-right"></i> Restart Dashboard</button>
|
|
1566
|
+
<button class="btn btn-gold" onclick="saveConfig(true)"><i class="fa-solid fa-floppy-disk"></i> Save and Update</button>
|
|
1567
|
+
</div>
|
|
1568
|
+
</div>
|
|
1753
1569
|
|
|
1754
1570
|
<div id="existingProjectBar" style="display:none;margin-bottom:1.25rem;padding:10px 14px;background:rgba(232,213,163,0.06);border:1px solid rgba(232,213,163,0.15);border-radius:8px;">
|
|
1755
|
-
<label style="font-size:0.72rem;font-weight:600;color:var(--text-muted);letter-spacing:0.04em;text-transform:uppercase;margin-bottom:6px;display:block;">
|
|
1571
|
+
<label style="font-size:0.72rem;font-weight:600;color:var(--text-muted);letter-spacing:0.04em;text-transform:uppercase;margin-bottom:6px;display:block;">Active project</label>
|
|
1756
1572
|
<select id="existingProjectSelect" onchange="loadExistingProject(this.value)" style="width:100%;padding:8px 10px;background:var(--bg-elevated);border:1px solid var(--border-subtle);border-radius:6px;color:var(--text-primary);font-size:0.82rem;font-family:var(--font-body);">
|
|
1757
1573
|
<option value="">New project</option>
|
|
1758
1574
|
</select>
|
|
1575
|
+
<div id="projectContextHint" style="margin-top:6px; font-size:0.65rem; color:var(--text-muted);">Changes here apply to the selected project.</div>
|
|
1759
1576
|
</div>
|
|
1760
1577
|
|
|
1761
1578
|
<div class="form-row">
|
|
@@ -1790,34 +1607,44 @@ Configure API keys and run a test crawl.</div>
|
|
|
1790
1607
|
</div>
|
|
1791
1608
|
</div>
|
|
1792
1609
|
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1610
|
+
<div style="margin-top:20px; padding:16px; background:var(--bg-elevated); border:1px solid var(--border-subtle); border-radius:var(--radius);">
|
|
1611
|
+
<div style="display:flex; flex-wrap:wrap; gap:12px; align-items:flex-start; justify-content:space-between; margin-bottom:14px;">
|
|
1612
|
+
<div>
|
|
1613
|
+
<div style="font-size:0.82rem; font-weight:600; color:var(--text-primary);">Competitors & owned surfaces</div>
|
|
1614
|
+
<div style="font-size:0.66rem; color:var(--text-muted); margin-top:4px;">Manage rival domains and owned subdomains without rebuilding the rest of the project.</div>
|
|
1615
|
+
</div>
|
|
1616
|
+
<div style="display:flex; gap:8px; flex-wrap:wrap;">
|
|
1617
|
+
<button class="btn" onclick="restartDashboard()"><i class="fa-solid fa-rotate-right"></i> Restart Dashboard</button>
|
|
1618
|
+
<button class="btn btn-gold" onclick="saveCompetitorsModule()"><i class="fa-solid fa-floppy-disk"></i> Save and Update</button>
|
|
1619
|
+
</div>
|
|
1620
|
+
</div>
|
|
1621
|
+
|
|
1622
|
+
<!-- Owned Subdomains -->
|
|
1623
|
+
<div class="form-group">
|
|
1624
|
+
<label>Owned Subdomains <span style="color:var(--text-muted); font-weight:300;">(optional)</span></label>
|
|
1625
|
+
<div id="ownedList"></div>
|
|
1626
|
+
<button class="btn btn-sm" onclick="addOwnedRow()" style="margin-top: 4px;"><i class="fa-solid fa-plus"></i> Add subdomain</button>
|
|
1627
|
+
</div>
|
|
1799
1628
|
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1629
|
+
<!-- Competitors -->
|
|
1630
|
+
<div class="form-group" style="margin-bottom:0;">
|
|
1631
|
+
<label>Competitor Domains</label>
|
|
1632
|
+
<div id="competitorList"></div>
|
|
1633
|
+
<button class="btn btn-sm" onclick="addCompetitorRow()" style="margin-top: 4px;"><i class="fa-solid fa-plus"></i> Add competitor</button>
|
|
1634
|
+
</div>
|
|
1635
|
+
|
|
1636
|
+
<div id="competitorModuleStatus" style="margin-top:10px; font-size:0.68rem; color:var(--text-muted);"></div>
|
|
1805
1637
|
</div>
|
|
1806
1638
|
|
|
1807
|
-
<!-- Crawl Settings -->
|
|
1639
|
+
<!-- Crawl Settings (simplified — stealth available via CLI --stealth flag) -->
|
|
1808
1640
|
<div class="form-row">
|
|
1809
1641
|
<div class="form-group">
|
|
1810
|
-
<label>Crawl
|
|
1811
|
-
<div
|
|
1812
|
-
<
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
<input type="radio" name="crawlMode" value="stealth"> Advanced
|
|
1817
|
-
</label>
|
|
1818
|
-
</div>
|
|
1819
|
-
<div id="crawlModeDesc" style="margin-top:8px; padding:8px 10px; background:var(--bg-elevated); border:1px solid var(--border-subtle); border-radius:var(--radius); font-size:0.68rem; color:var(--text-muted); line-height:1.5;">
|
|
1820
|
-
<strong style="color:var(--text-secondary);">Standard</strong> — Headless Chromium via Playwright. Fast (~1.5s/page). Respects robots.txt. Works on most sites. Use this unless you get blocked.
|
|
1642
|
+
<label>Crawl Engine</label>
|
|
1643
|
+
<div style="padding:8px 10px; background:var(--bg-elevated); border:1px solid var(--border-subtle); border-radius:var(--radius); font-size:0.68rem; color:var(--text-muted); line-height:1.5;">
|
|
1644
|
+
<strong style="color:var(--text-secondary);">Playwright</strong> — Headless Chromium. Fast (~1.5s/page). Respects robots.txt. Works on most sites.
|
|
1645
|
+
<span style="display:block; margin-top:4px; font-size:0.6rem; color:var(--text-muted);">
|
|
1646
|
+
<i class="fa-solid fa-terminal" style="margin-right:3px;"></i> For JS-heavy or blocking sites, use <code style="background:rgba(255,255,255,0.05);padding:1px 3px;border-radius:3px;">seo-intel crawl --stealth</code>
|
|
1647
|
+
</span>
|
|
1821
1648
|
</div>
|
|
1822
1649
|
|
|
1823
1650
|
<!-- Estimated throughput -->
|
|
@@ -1864,10 +1691,26 @@ Configure API keys and run a test crawl.</div>
|
|
|
1864
1691
|
<!-- ─── Step 4: Google Search Console ─────────────────────────────────── -->
|
|
1865
1692
|
<div class="step-panel" id="step4">
|
|
1866
1693
|
<div class="card">
|
|
1867
|
-
<
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1694
|
+
<div style="display:flex; flex-wrap:wrap; gap:12px; align-items:flex-start; justify-content:space-between; margin-bottom:16px;">
|
|
1695
|
+
<div>
|
|
1696
|
+
<h2 style="margin-bottom:6px;"><i class="fa-brands fa-google" style="color:#4285F4;"></i> Google Search Console</h2>
|
|
1697
|
+
<div class="hint" style="margin-bottom:0;">
|
|
1698
|
+
GSC data powers your ranking insights, CTR analysis, and wasted impressions detection.
|
|
1699
|
+
You can skip this step and add it later.
|
|
1700
|
+
</div>
|
|
1701
|
+
</div>
|
|
1702
|
+
<div style="display:flex; gap:8px; flex-wrap:wrap; align-items:center; justify-content:flex-end;">
|
|
1703
|
+
<button class="btn" onclick="restartDashboard()"><i class="fa-solid fa-rotate-right"></i> Restart Dashboard</button>
|
|
1704
|
+
<button class="btn btn-gold" onclick="refreshGscModule()"><i class="fa-solid fa-floppy-disk"></i> Save and Update</button>
|
|
1705
|
+
</div>
|
|
1706
|
+
</div>
|
|
1707
|
+
|
|
1708
|
+
<div style="margin-bottom:16px;padding:10px 14px;background:rgba(66,133,244,0.05);border:1px solid rgba(66,133,244,0.18);border-radius:8px;">
|
|
1709
|
+
<label style="font-size:0.72rem;font-weight:600;color:var(--text-muted);letter-spacing:0.04em;text-transform:uppercase;margin-bottom:6px;display:block;">Project</label>
|
|
1710
|
+
<select id="gscProjectSelect" onchange="changeActiveProjectFromGsc(this.value)" style="width:100%;padding:8px 10px;background:var(--bg-elevated);border:1px solid var(--border-subtle);border-radius:6px;color:var(--text-primary);font-size:0.82rem;font-family:var(--font-body);">
|
|
1711
|
+
<option value="">Select project</option>
|
|
1712
|
+
</select>
|
|
1713
|
+
<div id="gscProjectHint" style="margin-top:6px; font-size:0.65rem; color:var(--text-muted);">Uploads and status checks apply to the selected project.</div>
|
|
1871
1714
|
</div>
|
|
1872
1715
|
|
|
1873
1716
|
<!-- GSC Status -->
|
|
@@ -1947,7 +1790,16 @@ Configure API keys and run a test crawl.</div>
|
|
|
1947
1790
|
<!-- ─── Step 5: Pipeline Test ─────────────────────────────────────────── -->
|
|
1948
1791
|
<div class="step-panel" id="step5">
|
|
1949
1792
|
<div class="card">
|
|
1950
|
-
<
|
|
1793
|
+
<div style="display:flex; flex-wrap:wrap; gap:12px; align-items:flex-start; justify-content:space-between; margin-bottom:16px;">
|
|
1794
|
+
<div>
|
|
1795
|
+
<h2 style="margin-bottom:6px;"><i class="fa-solid fa-vial"></i> Pipeline Test</h2>
|
|
1796
|
+
<div class="hint" style="margin-bottom:0;">Run validation after model/auth changes and before a full production crawl.</div>
|
|
1797
|
+
</div>
|
|
1798
|
+
<div style="display:flex; gap:8px; flex-wrap:wrap; align-items:center; justify-content:flex-end;">
|
|
1799
|
+
<button class="btn" onclick="restartDashboard()"><i class="fa-solid fa-rotate-right"></i> Restart Dashboard</button>
|
|
1800
|
+
<button class="btn btn-gold" onclick="saveModelsModule()"><i class="fa-solid fa-floppy-disk"></i> Save Models/Auth</button>
|
|
1801
|
+
</div>
|
|
1802
|
+
</div>
|
|
1951
1803
|
|
|
1952
1804
|
<div id="testCards">
|
|
1953
1805
|
<div class="test-card" id="testOllama">
|
|
@@ -2007,21 +1859,125 @@ Configure API keys and run a test crawl.</div>
|
|
|
2007
1859
|
<!-- Populated by JS -->
|
|
2008
1860
|
</table>
|
|
2009
1861
|
|
|
2010
|
-
<h3 style="font-family:var(--font-display); font-size:0.85rem; color:var(--text-primary); margin: 20px 0
|
|
2011
|
-
<i class="fa-solid fa-
|
|
1862
|
+
<h3 style="font-family:var(--font-display); font-size:0.85rem; color:var(--text-primary); margin: 20px 0 12px; font-weight:600;">
|
|
1863
|
+
<i class="fa-solid fa-rocket" style="color:var(--accent-gold); margin-right:6px;"></i> What's Next
|
|
2012
1864
|
</h3>
|
|
2013
|
-
|
|
2014
|
-
|
|
1865
|
+
|
|
1866
|
+
<!-- Action cards instead of empty CLI -->
|
|
1867
|
+
<div id="doneActions" style="display:grid; grid-template-columns:1fr 1fr; gap:10px; margin-bottom:20px;">
|
|
1868
|
+
<a href="/" style="text-decoration:none; display:block; padding:14px; background:var(--bg-elevated); border:1px solid rgba(142,203,168,0.2); border-radius:var(--radius); text-align:center; transition:border-color 0.2s;" onmouseover="this.style.borderColor='rgba(142,203,168,0.5)'" onmouseout="this.style.borderColor='rgba(142,203,168,0.2)'">
|
|
1869
|
+
<i class="fa-solid fa-chart-line" style="font-size:1.2rem; color:var(--color-success); display:block; margin-bottom:6px;"></i>
|
|
1870
|
+
<div style="font-size:0.78rem; font-weight:500; color:var(--text-primary);">Open Dashboard</div>
|
|
1871
|
+
<div style="font-size:0.62rem; color:var(--text-muted); margin-top:2px;">Start crawling from the UI</div>
|
|
1872
|
+
</a>
|
|
1873
|
+
<div id="doneCliCard" style="padding:14px; background:var(--bg-elevated); border:1px solid var(--border-subtle); border-radius:var(--radius); text-align:center;">
|
|
1874
|
+
<i class="fa-solid fa-terminal" style="font-size:1.2rem; color:var(--accent-purple); display:block; margin-bottom:6px;"></i>
|
|
1875
|
+
<div style="font-size:0.78rem; font-weight:500; color:var(--text-primary);">Or via CLI</div>
|
|
1876
|
+
<div id="doneCliHint" style="font-size:0.62rem; color:var(--text-muted); margin-top:2px;"></div>
|
|
1877
|
+
</div>
|
|
2015
1878
|
</div>
|
|
2016
1879
|
|
|
2017
|
-
|
|
2018
|
-
|
|
1880
|
+
<!-- Compact CLI block (populated by JS) -->
|
|
1881
|
+
<div class="cli-block" id="cliBlock" style="font-size:0.68rem; line-height:1.6;">
|
|
1882
|
+
<button class="btn btn-sm copy-btn" onclick="copyCli()"><i class="fa-regular fa-copy"></i></button>
|
|
2019
1883
|
</div>
|
|
2020
1884
|
</div>
|
|
2021
1885
|
</div>
|
|
2022
1886
|
|
|
2023
1887
|
</div><!-- /wizard-body -->
|
|
2024
1888
|
|
|
1889
|
+
<!-- ═══ Genie Card — fixed floating agent/OpenClaw helper ═══════════════ -->
|
|
1890
|
+
<div class="card genie-card" id="genieCard" style="display:none;">
|
|
1891
|
+
<button class="genie-close" onclick="document.getElementById('genieCard').style.display='none'"><i class="fa-solid fa-xmark"></i></button>
|
|
1892
|
+
<div style="display:flex; align-items:center; gap:10px; margin-bottom:14px;">
|
|
1893
|
+
<svg width="24" height="24" viewBox="0 0 28 28" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
1894
|
+
<circle cx="14" cy="14" r="13" stroke="var(--accent-purple)" stroke-width="1.5" opacity="0.6"/>
|
|
1895
|
+
<path d="M9 12c0-2.8 2.2-5 5-5s5 2.2 5 5c0 1.8-1 3.4-2.4 4.3L18 20H10l1.4-3.7C10 15.4 9 13.8 9 12z" fill="var(--accent-purple)" opacity="0.8"/>
|
|
1896
|
+
</svg>
|
|
1897
|
+
<span style="font-size:0.92rem; font-weight:600; color:var(--text-primary);">OpenClaw</span>
|
|
1898
|
+
</div>
|
|
1899
|
+
|
|
1900
|
+
<!-- Tab switcher: Agentic Setup vs Cloud Models -->
|
|
1901
|
+
<div style="display:flex; gap:6px; margin-bottom:14px;">
|
|
1902
|
+
<button class="btn btn-sm genie-tab active" data-tab="agents" onclick="switchGenieTab('agents', this)" style="font-size:0.68rem; padding:5px 8px; flex:1;">
|
|
1903
|
+
<i class="fa-solid fa-wand-magic-sparkles" style="margin-right:4px;"></i>Agentic Setup
|
|
1904
|
+
</button>
|
|
1905
|
+
<button class="btn btn-sm genie-tab" data-tab="cloud" onclick="switchGenieTab('cloud', this)" style="font-size:0.68rem; padding:5px 8px; flex:1;">
|
|
1906
|
+
<i class="fa-solid fa-cloud" style="margin-right:4px;"></i>Cloud Models
|
|
1907
|
+
</button>
|
|
1908
|
+
</div>
|
|
1909
|
+
|
|
1910
|
+
<!-- Agentic Setup tab -->
|
|
1911
|
+
<div id="genieAgents">
|
|
1912
|
+
<div style="font-size:0.75rem; color:var(--text-muted); margin-bottom:10px;">Pick your agent. Copy the prompt. Done.</div>
|
|
1913
|
+
|
|
1914
|
+
<div style="display:flex; gap:5px; flex-wrap:wrap; margin-bottom:10px;" id="genieRuntimeTabs">
|
|
1915
|
+
<button class="btn btn-sm agent-runtime-tab active" data-runtime="openclaw" onclick="selectGenieRuntime('openclaw', this)" style="font-size:0.62rem; padding:4px 7px;">OpenClaw</button>
|
|
1916
|
+
<button class="btn btn-sm agent-runtime-tab" data-runtime="claudecode" onclick="selectGenieRuntime('claudecode', this)" style="font-size:0.62rem; padding:4px 7px;">Claude Code</button>
|
|
1917
|
+
<button class="btn btn-sm agent-runtime-tab" data-runtime="codex" onclick="selectGenieRuntime('codex', this)" style="font-size:0.62rem; padding:4px 7px;">Codex</button>
|
|
1918
|
+
<button class="btn btn-sm agent-runtime-tab" data-runtime="perplexity" onclick="selectGenieRuntime('perplexity', this)" style="font-size:0.62rem; padding:4px 7px;">Perplexity</button>
|
|
1919
|
+
</div>
|
|
1920
|
+
|
|
1921
|
+
<div id="genieRuntime-openclaw" class="genie-runtime-panel">
|
|
1922
|
+
<div style="padding:7px 9px; background:rgba(10,10,10,0.5); border:1px solid var(--border-subtle); border-radius:var(--radius); font-family:var(--font-mono); font-size:0.68rem; color:var(--text-secondary); display:flex; align-items:center; gap:6px; margin-bottom:8px;">
|
|
1923
|
+
<span style="color:var(--text-muted);">$</span>
|
|
1924
|
+
<span>clawhub install ukkometa/seo-intel</span>
|
|
1925
|
+
<button class="btn btn-sm" style="margin-left:auto; padding:2px 6px; font-size:0.55rem;" onclick="navigator.clipboard.writeText('clawhub install ukkometa/seo-intel');this.textContent='OK';setTimeout(()=>this.textContent='Copy',1200);">Copy</button>
|
|
1926
|
+
</div>
|
|
1927
|
+
<div style="position:relative; padding:7px 9px; background:rgba(10,10,10,0.5); border:1px solid var(--border-subtle); border-radius:var(--radius); font-family:var(--font-mono); font-size:0.62rem; color:var(--text-secondary); line-height:1.5;">
|
|
1928
|
+
<div id="genie-oc-prompt">Set up SEO Intel on my machine. Check deps, install Ollama, recommend a model, configure a project, run a test crawl.</div>
|
|
1929
|
+
<button class="btn btn-sm" style="position:absolute; top:5px; right:5px; padding:2px 5px; font-size:0.52rem;" onclick="navigator.clipboard.writeText(document.getElementById('genie-oc-prompt').textContent.trim());this.textContent='OK';setTimeout(()=>this.textContent='Copy',1200);">Copy</button>
|
|
1930
|
+
</div>
|
|
1931
|
+
<div style="display:flex; gap:6px; margin-top:10px;">
|
|
1932
|
+
<button class="btn btn-gold" onclick="startAgentSetup()" style="font-size:0.68rem; padding:6px 10px; flex:1;"><i class="fa-solid fa-play"></i> Start</button>
|
|
1933
|
+
<button class="btn" onclick="continueManualSetup()" style="font-size:0.68rem; padding:6px 10px;"><i class="fa-solid fa-list-check"></i> Manual</button>
|
|
1934
|
+
</div>
|
|
1935
|
+
</div>
|
|
1936
|
+
|
|
1937
|
+
<div id="genieRuntime-claudecode" class="genie-runtime-panel" style="display:none;">
|
|
1938
|
+
<div style="position:relative; padding:7px 9px; background:rgba(10,10,10,0.5); border:1px solid var(--border-subtle); border-radius:var(--radius); font-family:var(--font-mono); font-size:0.62rem; color:var(--text-secondary); line-height:1.5;">
|
|
1939
|
+
<div>Set up seo-intel: check Node 22.5+, install Ollama, pull gemma4:e4b, create config, run crawl.</div>
|
|
1940
|
+
<button class="btn btn-sm" style="position:absolute; top:5px; right:5px; padding:2px 5px; font-size:0.52rem;" onclick="navigator.clipboard.writeText(this.previousElementSibling.textContent.trim());this.textContent='OK';setTimeout(()=>this.textContent='Copy',1200);">Copy</button>
|
|
1941
|
+
</div>
|
|
1942
|
+
</div>
|
|
1943
|
+
|
|
1944
|
+
<div id="genieRuntime-codex" class="genie-runtime-panel" style="display:none;">
|
|
1945
|
+
<div style="position:relative; padding:7px 9px; background:rgba(10,10,10,0.5); border:1px solid var(--border-subtle); border-radius:var(--radius); font-family:var(--font-mono); font-size:0.62rem; color:var(--text-secondary); line-height:1.5;">
|
|
1946
|
+
<div>Install seo-intel deps (Node 22.5+, Ollama, gemma4:e4b), create project config, run: seo-intel crawl myproject. Full disk access.</div>
|
|
1947
|
+
<button class="btn btn-sm" style="position:absolute; top:5px; right:5px; padding:2px 5px; font-size:0.52rem;" onclick="navigator.clipboard.writeText(this.previousElementSibling.textContent.trim());this.textContent='OK';setTimeout(()=>this.textContent='Copy',1200);">Copy</button>
|
|
1948
|
+
</div>
|
|
1949
|
+
</div>
|
|
1950
|
+
|
|
1951
|
+
<div id="genieRuntime-perplexity" class="genie-runtime-panel" style="display:none;">
|
|
1952
|
+
<div style="position:relative; padding:7px 9px; background:rgba(10,10,10,0.5); border:1px solid var(--border-subtle); border-radius:var(--radius); font-family:var(--font-mono); font-size:0.62rem; color:var(--text-secondary); line-height:1.5;">
|
|
1953
|
+
<div>Help me set up SEO Intel: install Node.js 22.5+, Ollama, download Gemma 4 model, create config for my website. Ask for my URL.</div>
|
|
1954
|
+
<button class="btn btn-sm" style="position:absolute; top:5px; right:5px; padding:2px 5px; font-size:0.52rem;" onclick="navigator.clipboard.writeText(this.previousElementSibling.textContent.trim());this.textContent='OK';setTimeout(()=>this.textContent='Copy',1200);">Copy</button>
|
|
1955
|
+
</div>
|
|
1956
|
+
</div>
|
|
1957
|
+
</div>
|
|
1958
|
+
|
|
1959
|
+
<!-- Cloud Models tab -->
|
|
1960
|
+
<div id="genieCloud" style="display:none;">
|
|
1961
|
+
<p style="font-size:0.75rem; color:var(--text-muted); line-height:1.6; margin-bottom:12px;">
|
|
1962
|
+
Route analysis through frontier models. OpenClaw manages API keys and OAuth automatically.
|
|
1963
|
+
</p>
|
|
1964
|
+
<div style="font-size:0.72rem; color:var(--text-secondary);">
|
|
1965
|
+
<div style="display:flex; align-items:center; gap:6px; margin-bottom:6px;">
|
|
1966
|
+
<i class="fa-solid fa-check" style="color:var(--accent-purple); font-size:0.62rem; width:12px;"></i> Claude Opus 4.6
|
|
1967
|
+
</div>
|
|
1968
|
+
<div style="display:flex; align-items:center; gap:6px; margin-bottom:6px;">
|
|
1969
|
+
<i class="fa-solid fa-check" style="color:var(--accent-purple); font-size:0.62rem; width:12px;"></i> Gemini 3.1 Pro
|
|
1970
|
+
</div>
|
|
1971
|
+
<div style="display:flex; align-items:center; gap:6px; margin-bottom:6px;">
|
|
1972
|
+
<i class="fa-solid fa-check" style="color:var(--accent-purple); font-size:0.62rem; width:12px;"></i> GPT-5.4
|
|
1973
|
+
</div>
|
|
1974
|
+
</div>
|
|
1975
|
+
<p style="font-size:0.65rem; color:var(--text-muted); line-height:1.5; margin-top:10px;">
|
|
1976
|
+
No .env keys needed. OAuth-based model access means the pipeline test may show "no API key" — this is normal.
|
|
1977
|
+
</p>
|
|
1978
|
+
</div>
|
|
1979
|
+
</div>
|
|
1980
|
+
|
|
2025
1981
|
<!-- ─── Agent Chat Panel (shown when OpenClaw agent setup is chosen) ──── -->
|
|
2026
1982
|
<div class="agent-panel" id="agentPanel">
|
|
2027
1983
|
<div class="agent-chat">
|
|
@@ -2059,6 +2015,7 @@ Configure API keys and run a test crawl.</div>
|
|
|
2059
2015
|
savedApiProvider: null,
|
|
2060
2016
|
configSaved: false,
|
|
2061
2017
|
projectSlug: '',
|
|
2018
|
+
activeProject: '',
|
|
2062
2019
|
crawlMode: 'standard',
|
|
2063
2020
|
testsPassed: false,
|
|
2064
2021
|
gscFiles: [], // Files staged for upload
|
|
@@ -2232,24 +2189,9 @@ Configure API keys and run a test crawl.</div>
|
|
|
2232
2189
|
const nextBtn = document.getElementById('step1Next');
|
|
2233
2190
|
nextBtn.disabled = !status.node.meetsMinimum;
|
|
2234
2191
|
|
|
2235
|
-
// Show OpenClaw
|
|
2236
|
-
const
|
|
2237
|
-
|
|
2238
|
-
const bannerMin = document.getElementById('ocBannerMinimal');
|
|
2239
|
-
|
|
2240
|
-
if (status.openclaw?.canAgentSetup) {
|
|
2241
|
-
banner.style.display = '';
|
|
2242
|
-
bannerRec.style.display = 'flex';
|
|
2243
|
-
bannerMin.style.display = 'none';
|
|
2244
|
-
} else if (status.openclaw?.installed) {
|
|
2245
|
-
banner.style.display = '';
|
|
2246
|
-
bannerRec.style.display = 'none';
|
|
2247
|
-
bannerMin.style.display = 'flex';
|
|
2248
|
-
} else {
|
|
2249
|
-
banner.style.display = '';
|
|
2250
|
-
bannerRec.style.display = 'none';
|
|
2251
|
-
bannerMin.style.display = 'flex';
|
|
2252
|
-
}
|
|
2192
|
+
// Show genie card (floating agent/OpenClaw helper)
|
|
2193
|
+
const genie = document.getElementById('genieCard');
|
|
2194
|
+
if (genie) genie.style.display = '';
|
|
2253
2195
|
}
|
|
2254
2196
|
|
|
2255
2197
|
window.runSystemCheck = async function() {
|
|
@@ -2345,8 +2287,10 @@ Configure API keys and run a test crawl.</div>
|
|
|
2345
2287
|
const res = await API.get(`/api/setup/ping-ollama?host=${encodeURIComponent(host)}`);
|
|
2346
2288
|
if (res.reachable) {
|
|
2347
2289
|
result.innerHTML = `<span style="color:var(--color-success);"><i class="fa-solid fa-check"></i> Connected — ${res.models.length} model(s) found</span>`;
|
|
2348
|
-
//
|
|
2349
|
-
|
|
2290
|
+
// Append to comma-separated OLLAMA_HOSTS in .env
|
|
2291
|
+
const currentHosts = (state.systemStatus?.env?.raw?.OLLAMA_HOSTS || '').split(',').map(h => h.trim()).filter(Boolean);
|
|
2292
|
+
if (!currentHosts.includes(host)) currentHosts.push(host);
|
|
2293
|
+
await API.post('/api/setup/save-env', { key: 'OLLAMA_HOSTS', value: currentHosts.join(',') });
|
|
2350
2294
|
// Refresh status
|
|
2351
2295
|
const status = await API.get('/api/setup/status');
|
|
2352
2296
|
state.systemStatus = status;
|
|
@@ -2365,11 +2309,14 @@ Configure API keys and run a test crawl.</div>
|
|
|
2365
2309
|
};
|
|
2366
2310
|
|
|
2367
2311
|
window.removeOllamaHost = async function(host) {
|
|
2368
|
-
//
|
|
2369
|
-
|
|
2312
|
+
// Remove single host from comma-separated OLLAMA_HOSTS
|
|
2313
|
+
const currentHosts = (state.systemStatus?.env?.raw?.OLLAMA_HOSTS || '').split(',').map(h => h.trim()).filter(Boolean);
|
|
2314
|
+
const updated = currentHosts.filter(h => h !== host);
|
|
2315
|
+
await API.post('/api/setup/save-env', { key: 'OLLAMA_HOSTS', value: updated.join(',') });
|
|
2370
2316
|
const status = await API.get('/api/setup/status');
|
|
2371
2317
|
state.systemStatus = status;
|
|
2372
2318
|
renderOllamaHosts();
|
|
2319
|
+
loadModels();
|
|
2373
2320
|
};
|
|
2374
2321
|
|
|
2375
2322
|
// Render hosts when step 2 loads
|
|
@@ -2504,7 +2451,8 @@ Configure API keys and run a test crawl.</div>
|
|
|
2504
2451
|
function renderAnalysisModels() {
|
|
2505
2452
|
const div = document.getElementById('analysisModels');
|
|
2506
2453
|
div.innerHTML = '';
|
|
2507
|
-
|
|
2454
|
+
// Local models only — cloud models live in the Cloud Analysis column
|
|
2455
|
+
const models = [...(state.modelData.allAnalysis || [])].filter(m => m.type !== 'cloud');
|
|
2508
2456
|
// Sort: installed first, then recommended, then by minVramMB
|
|
2509
2457
|
models.sort((a, b) => (b.installed ? 1 : 0) - (a.installed ? 1 : 0) || (b.recommended ? 1 : 0) - (a.recommended ? 1 : 0) || a.minVramMB - b.minVramMB);
|
|
2510
2458
|
const rec = state.modelData.analysis;
|
|
@@ -2558,6 +2506,20 @@ Configure API keys and run a test crawl.</div>
|
|
|
2558
2506
|
if (pullRow) pullRow.style.display = 'none';
|
|
2559
2507
|
if (cloudHint) cloudHint.style.display = 'none';
|
|
2560
2508
|
|
|
2509
|
+
if (selected && selected.type === 'cloud') {
|
|
2510
|
+
state.selectedAnalysisProvider = selected.provider;
|
|
2511
|
+
state.selectedAnalysisModel = selected.id;
|
|
2512
|
+
const keyArea = document.getElementById('cloudKeyArea');
|
|
2513
|
+
const keyInput = document.getElementById('cloudApiKeyInput');
|
|
2514
|
+
if (keyArea && keyInput) {
|
|
2515
|
+
keyArea.style.display = '';
|
|
2516
|
+
keyInput.placeholder = `${selected.envKey}=...`;
|
|
2517
|
+
keyInput.dataset.envVar = selected.envKey;
|
|
2518
|
+
keyInput.dataset.provider = selected.provider;
|
|
2519
|
+
keyInput.dataset.model = selected.id;
|
|
2520
|
+
}
|
|
2521
|
+
}
|
|
2522
|
+
|
|
2561
2523
|
if (selected && !selected.installed) {
|
|
2562
2524
|
if (selected.type === 'cloud') {
|
|
2563
2525
|
// Cloud model — show API key hint
|
|
@@ -2628,6 +2590,60 @@ Configure API keys and run a test crawl.</div>
|
|
|
2628
2590
|
|
|
2629
2591
|
// ── Step 3: Project Configuration ─────────────────────────────────────
|
|
2630
2592
|
|
|
2593
|
+
function getActiveProject() {
|
|
2594
|
+
return state.activeProject || state.projectSlug || '';
|
|
2595
|
+
}
|
|
2596
|
+
|
|
2597
|
+
function syncProjectSelectors() {
|
|
2598
|
+
const active = getActiveProject();
|
|
2599
|
+
const selectors = [
|
|
2600
|
+
document.getElementById('existingProjectSelect'),
|
|
2601
|
+
document.getElementById('gscProjectSelect'),
|
|
2602
|
+
].filter(Boolean);
|
|
2603
|
+
|
|
2604
|
+
selectors.forEach(sel => {
|
|
2605
|
+
if (!sel) return;
|
|
2606
|
+
if (active && Array.from(sel.options).some(opt => opt.value === active)) {
|
|
2607
|
+
sel.value = active;
|
|
2608
|
+
} else if (!active && sel.id === 'existingProjectSelect') {
|
|
2609
|
+
sel.value = '';
|
|
2610
|
+
}
|
|
2611
|
+
});
|
|
2612
|
+
}
|
|
2613
|
+
|
|
2614
|
+
function ensureProjectOption(projectName) {
|
|
2615
|
+
if (!projectName) return;
|
|
2616
|
+
['existingProjectSelect', 'gscProjectSelect'].forEach(id => {
|
|
2617
|
+
const sel = document.getElementById(id);
|
|
2618
|
+
if (!sel) return;
|
|
2619
|
+
const exists = Array.from(sel.options).some(opt => opt.value === projectName);
|
|
2620
|
+
if (!exists) {
|
|
2621
|
+
const opt = document.createElement('option');
|
|
2622
|
+
opt.value = projectName;
|
|
2623
|
+
opt.textContent = projectName;
|
|
2624
|
+
sel.appendChild(opt);
|
|
2625
|
+
}
|
|
2626
|
+
});
|
|
2627
|
+
}
|
|
2628
|
+
|
|
2629
|
+
function setActiveProject(projectName) {
|
|
2630
|
+
state.activeProject = projectName || '';
|
|
2631
|
+
if (projectName) state.projectSlug = projectName;
|
|
2632
|
+
syncProjectSelectors();
|
|
2633
|
+
}
|
|
2634
|
+
|
|
2635
|
+
async function restartDashboard() {
|
|
2636
|
+
try {
|
|
2637
|
+
const res = await API.post('/api/setup/dashboard/restart', { project: getActiveProject() || null });
|
|
2638
|
+
alert(res.message || 'Dashboard restart requested. Reloading now.');
|
|
2639
|
+
window.location.reload();
|
|
2640
|
+
} catch (err) {
|
|
2641
|
+
alert('Dashboard restart failed: ' + err.message);
|
|
2642
|
+
}
|
|
2643
|
+
}
|
|
2644
|
+
|
|
2645
|
+
window.restartDashboard = restartDashboard;
|
|
2646
|
+
|
|
2631
2647
|
function slugify(s) {
|
|
2632
2648
|
return s.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '');
|
|
2633
2649
|
}
|
|
@@ -2636,23 +2652,17 @@ Configure API keys and run a test crawl.</div>
|
|
|
2636
2652
|
const name = document.getElementById('cfgProjectName').value;
|
|
2637
2653
|
const slug = slugify(name);
|
|
2638
2654
|
state.projectSlug = slug;
|
|
2655
|
+
if (!state.activeProject || state.activeProject === state.projectSlug) {
|
|
2656
|
+
state.activeProject = slug;
|
|
2657
|
+
}
|
|
2658
|
+
ensureProjectOption(slug);
|
|
2659
|
+
syncProjectSelectors();
|
|
2639
2660
|
document.getElementById('slugPreview').textContent = slug ? 'config/' + slug + '.json' : '';
|
|
2640
2661
|
};
|
|
2641
2662
|
|
|
2663
|
+
// Crawl mode is always 'standard' in setup — stealth available via CLI --stealth flag
|
|
2642
2664
|
window.selectCrawlMode = function(mode) {
|
|
2643
|
-
state.crawlMode = mode;
|
|
2644
|
-
document.querySelectorAll('#crawlModePills .pill-option').forEach(p => {
|
|
2645
|
-
p.classList.toggle('active', p.querySelector('input').value === mode);
|
|
2646
|
-
});
|
|
2647
|
-
// Update description
|
|
2648
|
-
const descEl = document.getElementById('crawlModeDesc');
|
|
2649
|
-
if (descEl) {
|
|
2650
|
-
const descs = {
|
|
2651
|
-
standard: '<strong style="color:var(--text-secondary);">Standard</strong> — Headless Chromium via Playwright. Fast (~1.5s/page). Respects robots.txt. Works on most sites. Use this unless you get blocked.',
|
|
2652
|
-
stealth: '<strong style="color:var(--text-secondary);">Advanced</strong> — Full browser rendering with enhanced compatibility. Persistent sessions, realistic browsing patterns. Slower (~3-4s/page). Use for JavaScript-heavy or dynamically-loaded sites.',
|
|
2653
|
-
};
|
|
2654
|
-
descEl.innerHTML = descs[mode] || descs.standard;
|
|
2655
|
-
}
|
|
2665
|
+
state.crawlMode = mode || 'standard';
|
|
2656
2666
|
updateCrawlEstimate();
|
|
2657
2667
|
};
|
|
2658
2668
|
|
|
@@ -2786,7 +2796,7 @@ Configure API keys and run a test crawl.</div>
|
|
|
2786
2796
|
return Array.from(inputs).map(i => i.value.trim()).filter(Boolean);
|
|
2787
2797
|
}
|
|
2788
2798
|
|
|
2789
|
-
window.saveConfig = async function() {
|
|
2799
|
+
window.saveConfig = async function(stayOnStep = false) {
|
|
2790
2800
|
const errEl = document.getElementById('configErrors');
|
|
2791
2801
|
errEl.textContent = '';
|
|
2792
2802
|
|
|
@@ -2835,7 +2845,14 @@ Configure API keys and run a test crawl.</div>
|
|
|
2835
2845
|
const res = await API.post('/api/setup/config', body);
|
|
2836
2846
|
state.configSaved = true;
|
|
2837
2847
|
state.projectSlug = slugify(projectName);
|
|
2838
|
-
|
|
2848
|
+
setActiveProject(state.projectSlug);
|
|
2849
|
+
ensureProjectOption(state.projectSlug);
|
|
2850
|
+
await loadExistingProjects();
|
|
2851
|
+
if (stayOnStep) {
|
|
2852
|
+
checkGscStatus();
|
|
2853
|
+
} else {
|
|
2854
|
+
goToStep(4);
|
|
2855
|
+
}
|
|
2839
2856
|
} catch (err) {
|
|
2840
2857
|
errEl.innerHTML = '<div>Save failed: ' + err.message + '</div>';
|
|
2841
2858
|
}
|
|
@@ -2849,37 +2866,66 @@ Configure API keys and run a test crawl.</div>
|
|
|
2849
2866
|
|
|
2850
2867
|
async function loadExistingProjects() {
|
|
2851
2868
|
try {
|
|
2852
|
-
const raw = await API.get('/api/projects');
|
|
2853
|
-
// Handle both {projects: [...]} and [...] formats
|
|
2869
|
+
const raw = await API.get('/api/setup/projects');
|
|
2854
2870
|
const projects = Array.isArray(raw) ? raw : (raw.projects || []);
|
|
2855
2871
|
if (!projects.length) return;
|
|
2856
2872
|
const bar = document.getElementById('existingProjectBar');
|
|
2857
|
-
const
|
|
2873
|
+
const projectSel = document.getElementById('existingProjectSelect');
|
|
2874
|
+
const gscSel = document.getElementById('gscProjectSelect');
|
|
2858
2875
|
bar.style.display = 'block';
|
|
2876
|
+
[projectSel, gscSel].forEach(sel => {
|
|
2877
|
+
if (!sel) return;
|
|
2878
|
+
const keepFirst = sel.options[0];
|
|
2879
|
+
sel.innerHTML = '';
|
|
2880
|
+
sel.appendChild(keepFirst);
|
|
2881
|
+
});
|
|
2859
2882
|
projects.forEach(p => {
|
|
2860
|
-
const
|
|
2861
|
-
|
|
2862
|
-
|
|
2863
|
-
|
|
2883
|
+
const label = p.project + ' (' + (p.target || p.domain || '?') + ')';
|
|
2884
|
+
[projectSel, gscSel].forEach(sel => {
|
|
2885
|
+
if (!sel) return;
|
|
2886
|
+
const opt = document.createElement('option');
|
|
2887
|
+
opt.value = p.project;
|
|
2888
|
+
opt.textContent = label;
|
|
2889
|
+
sel.appendChild(opt);
|
|
2890
|
+
});
|
|
2864
2891
|
});
|
|
2892
|
+
syncProjectSelectors();
|
|
2865
2893
|
} catch { /* not served, skip */ }
|
|
2866
2894
|
}
|
|
2867
2895
|
|
|
2868
2896
|
window.loadExistingProject = async function(projectName) {
|
|
2869
|
-
if (!projectName)
|
|
2897
|
+
if (!projectName) {
|
|
2898
|
+
setActiveProject('');
|
|
2899
|
+
return;
|
|
2900
|
+
}
|
|
2870
2901
|
try {
|
|
2871
2902
|
const full = await API.get('/api/setup/config/' + encodeURIComponent(projectName));
|
|
2872
2903
|
if (!full) return;
|
|
2873
2904
|
|
|
2905
|
+
setActiveProject(projectName);
|
|
2906
|
+
|
|
2874
2907
|
// Populate form fields
|
|
2875
2908
|
document.getElementById('cfgProjectName').value = full.project || '';
|
|
2876
2909
|
updateSlug();
|
|
2910
|
+
setActiveProject(projectName);
|
|
2877
2911
|
document.getElementById('cfgTargetUrl').value = full.target?.url || ('https://' + (full.target?.domain || ''));
|
|
2878
2912
|
document.getElementById('cfgSiteName').value = full.context?.siteName || '';
|
|
2879
2913
|
document.getElementById('cfgIndustry').value = full.context?.industry || '';
|
|
2880
2914
|
document.getElementById('cfgAudience').value = full.context?.audience || '';
|
|
2881
2915
|
document.getElementById('cfgGoal').value = full.context?.goal || '';
|
|
2882
2916
|
|
|
2917
|
+
const ownedList = document.getElementById('ownedList');
|
|
2918
|
+
ownedList.innerHTML = '';
|
|
2919
|
+
const owned = full.owned || [];
|
|
2920
|
+
if (owned.length) {
|
|
2921
|
+
owned.forEach(o => {
|
|
2922
|
+
addOwnedRow();
|
|
2923
|
+
const rows = ownedList.querySelectorAll('.dyn-list-item');
|
|
2924
|
+
const last = rows[rows.length - 1];
|
|
2925
|
+
if (last) last.querySelector('input').value = o.url || ('https://' + o.domain);
|
|
2926
|
+
});
|
|
2927
|
+
}
|
|
2928
|
+
|
|
2883
2929
|
// Clear and populate competitors
|
|
2884
2930
|
const compList = document.getElementById('competitorList');
|
|
2885
2931
|
compList.innerHTML = '';
|
|
@@ -2895,6 +2941,7 @@ Configure API keys and run a test crawl.</div>
|
|
|
2895
2941
|
}
|
|
2896
2942
|
|
|
2897
2943
|
updateTimeEstimate();
|
|
2944
|
+
checkGscStatus();
|
|
2898
2945
|
} catch (err) {
|
|
2899
2946
|
console.warn('Failed to load project config:', err);
|
|
2900
2947
|
}
|
|
@@ -2998,6 +3045,9 @@ Configure API keys and run a test crawl.</div>
|
|
|
2998
3045
|
const statusBox = document.getElementById('gscStatus');
|
|
2999
3046
|
const methodSelect = document.getElementById('gscMethodSelect');
|
|
3000
3047
|
const csvSection = document.getElementById('gscCsvSection');
|
|
3048
|
+
const activeProject = getActiveProject();
|
|
3049
|
+
|
|
3050
|
+
syncProjectSelectors();
|
|
3001
3051
|
|
|
3002
3052
|
statusBox.innerHTML = `
|
|
3003
3053
|
<div class="gsc-status-icon"><i class="fa-solid fa-spinner fa-spin"></i></div>
|
|
@@ -3005,7 +3055,7 @@ Configure API keys and run a test crawl.</div>
|
|
|
3005
3055
|
`;
|
|
3006
3056
|
|
|
3007
3057
|
try {
|
|
3008
|
-
const project =
|
|
3058
|
+
const project = activeProject || '';
|
|
3009
3059
|
const gsc = await API.get('/api/setup/gsc?project=' + encodeURIComponent(project));
|
|
3010
3060
|
|
|
3011
3061
|
if (gsc.hasData) {
|
|
@@ -3054,12 +3104,106 @@ Configure API keys and run a test crawl.</div>
|
|
|
3054
3104
|
csvSection.style.display = 'block';
|
|
3055
3105
|
}
|
|
3056
3106
|
|
|
3107
|
+
async function saveCompetitorsModule() {
|
|
3108
|
+
const activeProject = getActiveProject();
|
|
3109
|
+
const statusEl = document.getElementById('competitorModuleStatus');
|
|
3110
|
+
if (!activeProject) {
|
|
3111
|
+
if (statusEl) statusEl.textContent = 'Save the project first so competitors can attach to a real config.';
|
|
3112
|
+
return;
|
|
3113
|
+
}
|
|
3114
|
+
|
|
3115
|
+
const owned = getListValues('ownedList');
|
|
3116
|
+
const competitors = getListValues('competitorList');
|
|
3117
|
+
|
|
3118
|
+
if (statusEl) statusEl.textContent = 'Updating competitors and owned subdomains...';
|
|
3119
|
+
|
|
3120
|
+
try {
|
|
3121
|
+
const current = await API.get('/api/setup/projects/' + encodeURIComponent(activeProject));
|
|
3122
|
+
const currentCompetitors = (current.competitors || []).map(c => c.url || c.domain || c).filter(Boolean);
|
|
3123
|
+
const currentOwned = (current.owned || []).map(o => o.url || o.domain || o).filter(Boolean);
|
|
3124
|
+
|
|
3125
|
+
const toAdd = competitors.filter(x => !currentCompetitors.includes(x));
|
|
3126
|
+
const toRemove = currentCompetitors.filter(x => !competitors.includes(x));
|
|
3127
|
+
const toAddOwned = owned.filter(x => !currentOwned.includes(x));
|
|
3128
|
+
const toRemoveOwned = currentOwned.filter(x => !owned.includes(x));
|
|
3129
|
+
|
|
3130
|
+
await fetch('/api/setup/projects/' + encodeURIComponent(activeProject) + '/competitors', {
|
|
3131
|
+
method: 'PATCH',
|
|
3132
|
+
headers: { 'Content-Type': 'application/json' },
|
|
3133
|
+
body: JSON.stringify({ add: toAdd, remove: toRemove, addOwned: toAddOwned, removeOwned: toRemoveOwned }),
|
|
3134
|
+
}).then(async res => {
|
|
3135
|
+
const data = await res.json().catch(() => ({}));
|
|
3136
|
+
if (!res.ok) throw new Error(data.error || ('HTTP ' + res.status));
|
|
3137
|
+
return data;
|
|
3138
|
+
});
|
|
3139
|
+
|
|
3140
|
+
if (statusEl) statusEl.textContent = 'Competitors module updated for ' + activeProject + '.';
|
|
3141
|
+
} catch (err) {
|
|
3142
|
+
if (statusEl) statusEl.textContent = 'Competitors update failed: ' + err.message;
|
|
3143
|
+
}
|
|
3144
|
+
}
|
|
3145
|
+
|
|
3146
|
+
window.saveCompetitorsModule = saveCompetitorsModule;
|
|
3147
|
+
|
|
3148
|
+
async function saveModelsModule() {
|
|
3149
|
+
const payload = {};
|
|
3150
|
+
const statusEl = document.getElementById('modelsModuleStatus');
|
|
3151
|
+
if (state.selectedExtraction) payload.OLLAMA_MODEL = state.selectedExtraction;
|
|
3152
|
+
if (state.selectedAnalysisProvider && state.selectedAnalysisModel) {
|
|
3153
|
+
payload.ANALYSIS_PROVIDER = state.selectedAnalysisProvider;
|
|
3154
|
+
payload.ANALYSIS_MODEL = state.selectedAnalysisModel;
|
|
3155
|
+
}
|
|
3156
|
+
|
|
3157
|
+
const cloudInput = document.getElementById('cloudApiKeyInput');
|
|
3158
|
+
if (cloudInput && cloudInput.dataset && cloudInput.dataset.envVar) {
|
|
3159
|
+
const keyVal = cloudInput.value.trim();
|
|
3160
|
+
if (keyVal) payload[cloudInput.dataset.envVar] = keyVal;
|
|
3161
|
+
}
|
|
3162
|
+
|
|
3163
|
+
if (Object.keys(payload).length === 0) {
|
|
3164
|
+
if (statusEl) statusEl.textContent = 'No model/auth changes to save yet.';
|
|
3165
|
+
return;
|
|
3166
|
+
}
|
|
3167
|
+
|
|
3168
|
+
try {
|
|
3169
|
+
if (statusEl) statusEl.textContent = 'Saving model and auth settings...';
|
|
3170
|
+
await API.post('/api/setup/env', { keys: payload });
|
|
3171
|
+
if (statusEl) statusEl.textContent = 'Models/Auth updated.';
|
|
3172
|
+
} catch (err) {
|
|
3173
|
+
if (statusEl) statusEl.textContent = 'Saving models/auth failed: ' + err.message;
|
|
3174
|
+
}
|
|
3175
|
+
}
|
|
3176
|
+
|
|
3177
|
+
window.saveModelsModule = saveModelsModule;
|
|
3178
|
+
|
|
3057
3179
|
window.selectGscMethod = function(method) {
|
|
3058
3180
|
state.gscMethod = method;
|
|
3059
3181
|
document.querySelectorAll('.gsc-method-card').forEach(c => c.classList.remove('active'));
|
|
3060
3182
|
event.currentTarget.classList.add('active');
|
|
3061
3183
|
};
|
|
3062
3184
|
|
|
3185
|
+
window.changeActiveProjectFromGsc = async function(projectName) {
|
|
3186
|
+
if (!projectName) return;
|
|
3187
|
+
await loadExistingProject(projectName);
|
|
3188
|
+
goToStep(4);
|
|
3189
|
+
};
|
|
3190
|
+
|
|
3191
|
+
window.refreshGscModule = async function() {
|
|
3192
|
+
const activeProject = getActiveProject();
|
|
3193
|
+
if (!activeProject) {
|
|
3194
|
+
alert('Pick or save a project first.');
|
|
3195
|
+
return;
|
|
3196
|
+
}
|
|
3197
|
+
|
|
3198
|
+
if (state.gscFiles.length > 0) {
|
|
3199
|
+
await uploadGscFiles();
|
|
3200
|
+
return;
|
|
3201
|
+
}
|
|
3202
|
+
|
|
3203
|
+
await checkGscStatus();
|
|
3204
|
+
alert('GSC module updated for ' + activeProject + '.');
|
|
3205
|
+
};
|
|
3206
|
+
|
|
3063
3207
|
// File handling
|
|
3064
3208
|
const VALID_GSC_FILES = ['Chart.csv', 'Queries.csv', 'Pages.csv', 'Countries.csv', 'Devices.csv', 'Search appearance.csv', 'Filters.csv'];
|
|
3065
3209
|
|
|
@@ -3114,7 +3258,7 @@ Configure API keys and run a test crawl.</div>
|
|
|
3114
3258
|
|
|
3115
3259
|
window.uploadGscFiles = async function() {
|
|
3116
3260
|
const statusEl = document.getElementById('gscUploadStatus');
|
|
3117
|
-
const project =
|
|
3261
|
+
const project = getActiveProject() || 'default';
|
|
3118
3262
|
|
|
3119
3263
|
statusEl.innerHTML = '<i class="fa-solid fa-spinner fa-spin"></i> Uploading...';
|
|
3120
3264
|
|
|
@@ -3162,26 +3306,22 @@ Configure API keys and run a test crawl.</div>
|
|
|
3162
3306
|
`<tr><td>${r[0]}</td><td>${r[1]}</td></tr>`
|
|
3163
3307
|
).join('');
|
|
3164
3308
|
|
|
3165
|
-
// Populate CLI block — tier-aware
|
|
3309
|
+
// Populate CLI block and hint — tier-aware
|
|
3166
3310
|
const block = document.getElementById('cliBlock');
|
|
3167
3311
|
const copyBtn = block.querySelector('.copy-btn').outerHTML;
|
|
3312
|
+
const hintEl = document.getElementById('doneCliHint');
|
|
3168
3313
|
|
|
3169
3314
|
if (state.tier === 'solo' || state.tier === 'agency') {
|
|
3315
|
+
hintEl.textContent = `seo-intel crawl ${slug}`;
|
|
3170
3316
|
block.innerHTML = copyBtn +
|
|
3171
|
-
`<span class="
|
|
3172
|
-
`<span class="prompt">$</span> seo-intel
|
|
3173
|
-
`<span class="comment"># AI gap analysis</span>\n` +
|
|
3174
|
-
`<span class="prompt">$</span> seo-intel analyze ${slug}\n\n` +
|
|
3175
|
-
`<span class="comment"># Open dashboard</span>\n` +
|
|
3317
|
+
`<span class="prompt">$</span> seo-intel crawl ${slug}\n` +
|
|
3318
|
+
`<span class="prompt">$</span> seo-intel analyze ${slug}\n` +
|
|
3176
3319
|
`<span class="prompt">$</span> seo-intel serve`;
|
|
3177
3320
|
} else {
|
|
3321
|
+
hintEl.textContent = `seo-intel crawl ${slug}`;
|
|
3178
3322
|
block.innerHTML = copyBtn +
|
|
3179
|
-
`<span class="
|
|
3180
|
-
`<span class="prompt">$</span> seo-intel
|
|
3181
|
-
`<span class="comment"># Browse your data</span>\n` +
|
|
3182
|
-
`<span class="prompt">$</span> seo-intel serve\n\n` +
|
|
3183
|
-
`<span class="comment"># Upgrade to Solo for AI analysis + dashboards</span>\n` +
|
|
3184
|
-
`<span class="comment"># ukkometa.fi/seo-intel</span>`;
|
|
3323
|
+
`<span class="prompt">$</span> seo-intel crawl ${slug}\n` +
|
|
3324
|
+
`<span class="prompt">$</span> seo-intel serve`;
|
|
3185
3325
|
}
|
|
3186
3326
|
|
|
3187
3327
|
goToStep(6);
|
|
@@ -3207,6 +3347,22 @@ Configure API keys and run a test crawl.</div>
|
|
|
3207
3347
|
let agentHistory = [];
|
|
3208
3348
|
let agentBusy = false;
|
|
3209
3349
|
|
|
3350
|
+
// ── Genie Card Functions ────────────────────────────────────────────
|
|
3351
|
+
window.switchGenieTab = function(tab, btn) {
|
|
3352
|
+
document.querySelectorAll('.genie-tab').forEach(b => b.classList.remove('active'));
|
|
3353
|
+
btn.classList.add('active');
|
|
3354
|
+
document.getElementById('genieAgents').style.display = tab === 'agents' ? '' : 'none';
|
|
3355
|
+
document.getElementById('genieCloud').style.display = tab === 'cloud' ? '' : 'none';
|
|
3356
|
+
};
|
|
3357
|
+
|
|
3358
|
+
window.selectGenieRuntime = function(runtime, btn) {
|
|
3359
|
+
document.querySelectorAll('#genieRuntimeTabs .agent-runtime-tab').forEach(b => b.classList.remove('active'));
|
|
3360
|
+
btn.classList.add('active');
|
|
3361
|
+
document.querySelectorAll('.genie-runtime-panel').forEach(p => p.style.display = 'none');
|
|
3362
|
+
const panel = document.getElementById('genieRuntime-' + runtime);
|
|
3363
|
+
if (panel) panel.style.display = '';
|
|
3364
|
+
};
|
|
3365
|
+
|
|
3210
3366
|
window.selectAgentRuntime = function(runtime, btn) {
|
|
3211
3367
|
document.querySelectorAll('.agent-runtime-tab').forEach(b => b.classList.remove('active'));
|
|
3212
3368
|
btn.classList.add('active');
|
|
@@ -3224,9 +3380,10 @@ Configure API keys and run a test crawl.</div>
|
|
|
3224
3380
|
};
|
|
3225
3381
|
|
|
3226
3382
|
window.startAgentSetup = function() {
|
|
3227
|
-
// Hide wizard steps, show agent panel
|
|
3383
|
+
// Hide wizard steps + genie, show agent panel
|
|
3228
3384
|
document.querySelector('.wizard-body').style.display = 'none';
|
|
3229
3385
|
document.querySelector('.step-indicator').style.display = 'none';
|
|
3386
|
+
const genie = document.getElementById('genieCard'); if (genie) genie.style.display = 'none';
|
|
3230
3387
|
document.getElementById('agentPanel').classList.add('active');
|
|
3231
3388
|
|
|
3232
3389
|
agentHistory = [];
|
|
@@ -3245,7 +3402,7 @@ Configure API keys and run a test crawl.</div>
|
|
|
3245
3402
|
|
|
3246
3403
|
window.continueManualSetup = function() {
|
|
3247
3404
|
// Just hide the banner and let user proceed with wizard
|
|
3248
|
-
document.getElementById('
|
|
3405
|
+
const genie = document.getElementById('genieCard'); if (genie) genie.style.display = 'none';
|
|
3249
3406
|
};
|
|
3250
3407
|
|
|
3251
3408
|
window.sendAgentMessage = function() {
|