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/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:center; justify-content:space-between; margin-bottom:4px;">
1443
- <h2 style="margin-bottom:0;"><i class="fa-solid fa-microchip"></i> Model Selection</h2>
1444
- <button class="btn btn-sm" onclick="document.getElementById('openclawFloating').style.display=''" style="font-size:0.65rem; color:var(--accent-purple); border-color:rgba(124,109,235,0.3); background:rgba(124,109,235,0.06);">
1445
- <i class="fa-solid fa-wand-magic-sparkles" style="margin-right:4px;"></i>OpenClaw
1446
- </button>
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
- <!-- OpenClaw floating sidebar -->
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
- <h2><i class="fa-solid fa-folder-open"></i> Project Configuration</h2>
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;">Edit existing project</label>
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
- <!-- Owned Subdomains -->
1794
- <div class="form-group">
1795
- <label>Owned Subdomains <span style="color:var(--text-muted); font-weight:300;">(optional)</span></label>
1796
- <div id="ownedList"></div>
1797
- <button class="btn btn-sm" onclick="addOwnedRow()" style="margin-top: 4px;"><i class="fa-solid fa-plus"></i> Add subdomain</button>
1798
- </div>
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
- <!-- Competitors -->
1801
- <div class="form-group">
1802
- <label>Competitor Domains</label>
1803
- <div id="competitorList"></div>
1804
- <button class="btn btn-sm" onclick="addCompetitorRow()" style="margin-top: 4px;"><i class="fa-solid fa-plus"></i> Add competitor</button>
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 Mode</label>
1811
- <div class="pill-group" id="crawlModePills">
1812
- <label class="pill-option active" onclick="selectCrawlMode('standard')">
1813
- <input type="radio" name="crawlMode" value="standard" checked> Standard
1814
- </label>
1815
- <label class="pill-option" onclick="selectCrawlMode('stealth')">
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
- <h2><i class="fa-brands fa-google" style="color:#4285F4;"></i> Google Search Console</h2>
1868
- <div class="hint" style="margin-bottom: 16px;">
1869
- GSC data powers your ranking insights, CTR analysis, and wasted impressions detection.
1870
- You can skip this step and add it later.
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
- <h2><i class="fa-solid fa-vial"></i> Pipeline Test</h2>
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 10px; font-weight:600;">
2011
- <i class="fa-solid fa-terminal" style="color:var(--accent-purple); margin-right:6px;"></i> Next Steps
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
- <div class="cli-block" id="cliBlock">
2014
- <button class="btn btn-sm copy-btn" onclick="copyCli()"><i class="fa-regular fa-copy"></i></button>
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
- <div style="text-align:center; margin-top: 24px;">
2018
- <a href="/" class="btn btn-gold" style="text-decoration:none;"><i class="fa-solid fa-chart-line"></i> Open Dashboard</a>
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 banner
2236
- const banner = document.getElementById('openclawBanner');
2237
- const bannerRec = document.getElementById('ocBannerRecommended');
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
- // Add to .env via server
2349
- await API.post('/api/setup/save-env', { key: 'OLLAMA_FALLBACK_URL', value: host });
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
- // Clear from .env
2369
- await API.post('/api/setup/save-env', { key: 'OLLAMA_FALLBACK_URL', value: '' });
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
- const models = [...(state.modelData.allAnalysis || [])];
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
- goToStep(4);
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 sel = document.getElementById('existingProjectSelect');
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 opt = document.createElement('option');
2861
- opt.value = p.project;
2862
- opt.textContent = p.project + ' (' + (p.target || p.domain || '?') + ')';
2863
- sel.appendChild(opt);
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) return;
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 = state.projectSlug || '';
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 = state.projectSlug || 'default';
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="comment"># Run your first crawl</span>\n` +
3172
- `<span class="prompt">$</span> seo-intel crawl ${slug}\n\n` +
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="comment"># Run your first crawl</span>\n` +
3180
- `<span class="prompt">$</span> seo-intel crawl ${slug}\n\n` +
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('openclawBanner').style.display = 'none';
3405
+ const genie = document.getElementById('genieCard'); if (genie) genie.style.display = 'none';
3249
3406
  };
3250
3407
 
3251
3408
  window.sendAgentMessage = function() {