seo-intel 1.1.4 → 1.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,27 +1,15 @@
1
1
  #!/bin/bash
2
- cd "$(dirname "$0")" || cd ~
3
- clear
2
+ cd "$(dirname "$0")"
4
3
  echo ""
5
- echo " 🦀 SEO Intel — Setup Wizard"
6
- echo " Opening in your browser..."
4
+ echo " SEO Intel — Setup Wizard"
5
+ echo " Opening setup in your browser..."
7
6
  echo ""
8
-
9
- # Start server in background if not already running
10
- if ! curl -s http://localhost:3000/ > /dev/null 2>&1; then
11
- npx seo-intel serve &
12
- SERVER_PID=$!
13
- sleep 2
14
- fi
15
-
16
- # Open setup wizard in browser
17
- open "http://localhost:3000/setup" 2>/dev/null || xdg-open "http://localhost:3000/setup" 2>/dev/null
18
-
19
- echo " Setup wizard is open at http://localhost:3000/setup"
20
- echo " Keep this window open while using the wizard."
21
- echo ""
22
- read -n 1 -s -r -p " Press any key to stop the server and exit..."
23
-
24
- # Clean up
25
- if [ -n "$SERVER_PID" ]; then
26
- kill $SERVER_PID 2>/dev/null
27
- fi
7
+ node cli.js serve &
8
+ SERVER_PID=$!
9
+ # Wait for server to be ready
10
+ for i in {1..10}; do
11
+ sleep 1
12
+ if curl -s http://localhost:3000/ > /dev/null 2>&1; then break; fi
13
+ done
14
+ open "http://localhost:3000/setup"
15
+ wait $SERVER_PID
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "seo-intel",
3
- "version": "1.1.4",
3
+ "version": "1.1.5",
4
4
  "description": "Local Ahrefs-style SEO competitor intelligence. Crawl → SQLite → cloud analysis.",
5
5
  "type": "module",
6
6
  "license": "SEE LICENSE IN LICENSE",
@@ -2188,6 +2188,53 @@ function buildHtmlTemplate(data, opts = {}) {
2188
2188
  if (scopeIdx > -1 && parts[scopeIdx + 1]) extra.scope = parts[scopeIdx + 1];
2189
2189
  runCommand(cmd, proj, extra);
2190
2190
  });
2191
+
2192
+ // Autorun: if URL has ?autorun=setup-classic, fire seo-intel setup --classic via SSE
2193
+ (function() {
2194
+ const urlParams = new URLSearchParams(window.location.search);
2195
+ if (urlParams.get('autorun') === 'setup-classic') {
2196
+ // Remove the param so it doesn't re-trigger on refresh
2197
+ window.history.replaceState({}, '', window.location.pathname);
2198
+ // Wait a tick for the panel to be ready, then stream the command
2199
+ setTimeout(function() {
2200
+ if (!isServed) {
2201
+ appendLine('Not connected to server. Cannot run setup --classic automatically.', 'error');
2202
+ return;
2203
+ }
2204
+ running = true;
2205
+ status.textContent = 'running...';
2206
+ status.style.color = 'var(--color-warning)';
2207
+ appendLine('$ seo-intel setup --classic', 'cmd');
2208
+ const params = new URLSearchParams({ command: 'setup', classic: 'true' });
2209
+ eventSource = new EventSource('/api/terminal?' + params.toString());
2210
+ eventSource.onmessage = function(e) {
2211
+ try {
2212
+ const msg = JSON.parse(e.data);
2213
+ if (msg.type === 'stdout') appendLine(msg.data, 'stdout');
2214
+ else if (msg.type === 'stderr') appendLine(msg.data, 'stderr');
2215
+ else if (msg.type === 'error') { appendLine('Error: ' + msg.data, 'error'); }
2216
+ else if (msg.type === 'exit') {
2217
+ const code = msg.data?.code ?? msg.data;
2218
+ appendLine(code === 0 ? 'Done.' : 'Exited with code ' + code, code === 0 ? 'exit-ok' : 'exit-err');
2219
+ running = false;
2220
+ status.textContent = code === 0 ? 'done' : 'failed';
2221
+ status.style.color = code === 0 ? 'var(--color-success)' : 'var(--color-danger)';
2222
+ eventSource.close();
2223
+ eventSource = null;
2224
+ }
2225
+ } catch (_) {}
2226
+ };
2227
+ eventSource.onerror = function() {
2228
+ if (running) { appendLine('Connection lost.', 'error'); }
2229
+ running = false;
2230
+ status.textContent = 'disconnected';
2231
+ status.style.color = 'var(--color-danger)';
2232
+ eventSource?.close();
2233
+ eventSource = null;
2234
+ };
2235
+ }, 300);
2236
+ }
2237
+ })();
2191
2238
  })();
2192
2239
  </script>
2193
2240
 
@@ -3499,7 +3546,6 @@ function buildHtmlTemplate(data, opts = {}) {
3499
3546
  };
3500
3547
 
3501
3548
  window.stopJob = function() {
3502
- if (!confirm('Stop the running job?')) return;
3503
3549
  fetch('/api/stop', { method: 'POST' })
3504
3550
  .then(function(r) { return r.json(); })
3505
3551
  .then(function(data) {
@@ -3582,6 +3628,11 @@ function buildHtmlTemplate(data, opts = {}) {
3582
3628
  dot.className = 'es-dot';
3583
3629
  label.style.color = 'var(--color-success)';
3584
3630
  label.textContent = 'Completed (' + (data.extracted || 0) + ' extracted' + (data.failed ? ', ' + data.failed + ' failed' : '') + ')';
3631
+ } else if (data.status === 'stopped') {
3632
+ panel.classList.remove('is-running', 'is-crashed');
3633
+ dot.className = 'es-dot';
3634
+ label.style.color = 'var(--accent-gold)';
3635
+ label.textContent = 'Stopped' + (data.extracted ? ' (' + data.extracted + ' extracted)' : '');
3585
3636
  } else if (data.status === 'crashed') {
3586
3637
  panel.classList.remove('is-running');
3587
3638
  panel.classList.add('is-crashed');
@@ -4691,7 +4742,6 @@ function buildMultiHtmlTemplate(allProjectData) {
4691
4742
  };
4692
4743
 
4693
4744
  window.stopJob = function() {
4694
- if (!confirm('Stop the running job?')) return;
4695
4745
  fetch('/api/stop', { method: 'POST' })
4696
4746
  .then(function(r) { return r.json(); })
4697
4747
  .then(function(data) {
@@ -4750,6 +4800,10 @@ function buildMultiHtmlTemplate(allProjectData) {
4750
4800
  panel.classList.remove('is-running', 'is-crashed');
4751
4801
  dot.className = 'es-dot'; label.style.color = 'var(--color-success)';
4752
4802
  label.textContent = 'Completed (' + (data.extracted || 0) + ' extracted)';
4803
+ } else if (data.status === 'stopped') {
4804
+ panel.classList.remove('is-running', 'is-crashed');
4805
+ dot.className = 'es-dot'; label.style.color = 'var(--accent-gold)';
4806
+ label.textContent = 'Stopped' + (data.extracted ? ' (' + data.extracted + ' extracted)' : '');
4753
4807
  } else if (data.status === 'crashed') {
4754
4808
  panel.classList.remove('is-running'); panel.classList.add('is-crashed');
4755
4809
  dot.className = 'es-dot crashed'; label.style.color = 'var(--color-danger)';
package/server.js CHANGED
@@ -354,6 +354,15 @@ async function handleRequest(req, res) {
354
354
  if (e.code !== 'ESRCH') throw e;
355
355
  // Already dead
356
356
  }
357
+ // Update progress file to reflect stopped state
358
+ try {
359
+ writeFileSync(PROGRESS_FILE, JSON.stringify({
360
+ ...progress,
361
+ status: 'stopped',
362
+ stopped_at: Date.now(),
363
+ updated_at: Date.now(),
364
+ }, null, 2));
365
+ } catch { /* best-effort */ }
357
366
  json(res, 200, { stopped: true, pid: progress.pid, command: progress.command });
358
367
  } catch (e) {
359
368
  json(res, 500, { error: e.message });
@@ -66,13 +66,39 @@ async function askAgent(messages, opts = {}) {
66
66
  */
67
67
  export async function isGatewayReady() {
68
68
  try {
69
+ // Try to read token from env or openclaw config file
70
+ let token = process.env.OPENCLAW_TOKEN;
71
+ if (!token) {
72
+ try {
73
+ const configPath = join(process.env.HOME || '~', '.openclaw', 'openclaw.json');
74
+ const { readFileSync } = await import('fs');
75
+ const raw = readFileSync(configPath, 'utf8');
76
+ // Gateway auth token is a 48-char hex string in the gateway.auth block
77
+ const matches = [...raw.matchAll(/"token":\s*"([a-f0-9]{40,})"/g)];
78
+ if (matches.length > 0) token = matches[matches.length - 1][1];
79
+ } catch {}
80
+ }
81
+ if (!token) return false;
82
+
83
+ // Verify gateway is reachable via a lightweight chat completions ping
69
84
  const controller = new AbortController();
70
- const timeout = setTimeout(() => controller.abort(), 3000);
71
- const res = await fetch(`${OPENCLAW_API}/v1/models`, {
85
+ const timeout = setTimeout(() => controller.abort(), 5000);
86
+ const res = await fetch(`${OPENCLAW_API}/v1/chat/completions`, {
87
+ method: 'POST',
72
88
  signal: controller.signal,
89
+ headers: {
90
+ 'Authorization': `Bearer ${token}`,
91
+ 'Content-Type': 'application/json',
92
+ },
93
+ body: JSON.stringify({
94
+ model: 'anthropic/claude-haiku-4-5',
95
+ messages: [{ role: 'user', content: 'ping' }],
96
+ max_tokens: 1,
97
+ }),
73
98
  });
74
99
  clearTimeout(timeout);
75
- return res.ok;
100
+ const ct = res.headers.get('content-type') || '';
101
+ return res.ok && ct.includes('application/json');
76
102
  } catch {
77
103
  return false;
78
104
  }
package/setup/wizard.html CHANGED
@@ -54,6 +54,7 @@ body {
54
54
  max-width: var(--max-width);
55
55
  margin: 0 auto 24px;
56
56
  text-align: center;
57
+ position: relative;
57
58
  }
58
59
  .wizard-header h1 {
59
60
  font-family: var(--font-display);
@@ -1172,6 +1173,7 @@ input::placeholder {
1172
1173
  <div class="wizard-header">
1173
1174
  <h1>SEO Intel</h1>
1174
1175
  <div class="subtitle">Setup Wizard</div>
1176
+ <a href="http://localhost:3000/?autorun=setup-classic" title="Run setup in Terminal instead" style="position:absolute;top:14px;right:18px;font-size:0.68rem;color:var(--color-muted,#888);text-decoration:none;opacity:0.7;transition:opacity 0.15s;" onmouseover="this.style.opacity='1'" onmouseout="this.style.opacity='0.7'"><i class="fa-solid fa-terminal"></i> Terminal setup</a>
1175
1177
  </div>
1176
1178
 
1177
1179
  <!-- ═══════════════════════════════════════════════════════════════════════