hyperclaw 5.2.2 → 5.2.4

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.
Files changed (41) hide show
  1. package/README.md +11 -0
  2. package/dist/chat-Bgyq3-wI.js +324 -0
  3. package/dist/chat-ChYGHacA.js +324 -0
  4. package/dist/daemon-BhAitpVR.js +404 -0
  5. package/dist/daemon-DD7qc2LI.js +7 -0
  6. package/dist/daemon-DY73xDpS.js +7 -0
  7. package/dist/daemon-jRZ0qvfS.js +404 -0
  8. package/dist/engine-CJtV0X2-.js +7 -0
  9. package/dist/engine-COYE-fVt.js +323 -0
  10. package/dist/engine-CSX8AcAZ.js +7 -0
  11. package/dist/engine-CkGhm_WF.js +323 -0
  12. package/dist/hyperclawbot-BFRUbPK-.js +508 -0
  13. package/dist/hyperclawbot-sfzAMwh-.js +508 -0
  14. package/dist/mcp-loader-D_NWYUqh.js +93 -0
  15. package/dist/mcp-loader-SiJk_kV8.js +93 -0
  16. package/dist/onboard-D4O-orEF.js +13 -0
  17. package/dist/onboard-DMTgAKzs.js +3865 -0
  18. package/dist/onboard-DN3bnyoq.js +3865 -0
  19. package/dist/onboard-I3AirQv9.js +13 -0
  20. package/dist/orchestrator-6ljWiZWu.js +189 -0
  21. package/dist/orchestrator-Baz5cuUr.js +189 -0
  22. package/dist/orchestrator-DfyK-2bP.js +6 -0
  23. package/dist/orchestrator-mdElwt2i.js +6 -0
  24. package/dist/run-main.js +25 -25
  25. package/dist/server-Buqn3qvG.js +1294 -0
  26. package/dist/server-D03HVwtV.js +4 -0
  27. package/dist/server-DG804qOP.js +4 -0
  28. package/dist/server-jaMeUOfM.js +1294 -0
  29. package/dist/skill-runtime-CZ8uTBra.js +102 -0
  30. package/dist/skill-runtime-D-vgx1zp.js +5 -0
  31. package/dist/skill-runtime-Dt3Zoj8M.js +102 -0
  32. package/dist/skill-runtime-DwNX3pnp.js +5 -0
  33. package/dist/src-BncJl7wS.js +458 -0
  34. package/dist/src-D0vSZWOd.js +458 -0
  35. package/dist/src-ij58SST0.js +63 -0
  36. package/dist/src-prH27XOV.js +63 -0
  37. package/dist/sub-agent-tools-DYKPR3aI.js +39 -0
  38. package/dist/sub-agent-tools-DrSkn7Ns.js +39 -0
  39. package/package.json +2 -1
  40. package/static/chat.html +340 -0
  41. package/static/dashboard.html +146 -0
@@ -0,0 +1,340 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en" data-theme="dark">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>HyperClaw Chat</title>
7
+ <script src="https://cdn.tailwindcss.com"></script>
8
+ <script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
9
+ <link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;700&family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
10
+ <style>
11
+ :root {
12
+ --bg: #090e1a;
13
+ --bg2: #0d1526;
14
+ --bg3: #111d33;
15
+ --border: #1e2d4a;
16
+ --accent: #00c2ff;
17
+ --accent2: #0077b6;
18
+ --text: #e2eaf8;
19
+ --text2: #8ba0c0;
20
+ --user-bg: linear-gradient(135deg, #0077b6, #00c2ff);
21
+ --asst-bg: #111d33;
22
+ --header-bg: rgba(9,14,26,0.95);
23
+ --shadow: 0 4px 24px rgba(0,194,255,0.08);
24
+ }
25
+ [data-theme="light"] {
26
+ --bg: #eef4ff;
27
+ --bg2: #dde8ff;
28
+ --bg3: #ffffff;
29
+ --border: #b8cdf0;
30
+ --accent: #0077b6;
31
+ --accent2: #005f99;
32
+ --text: #0d1d3a;
33
+ --text2: #3a5580;
34
+ --user-bg: linear-gradient(135deg, #0077b6, #00aee0);
35
+ --asst-bg: #ffffff;
36
+ --header-bg: rgba(238,244,255,0.97);
37
+ --shadow: 0 4px 24px rgba(0,119,182,0.10);
38
+ }
39
+ * { box-sizing: border-box; margin: 0; padding: 0; }
40
+ body { background: var(--bg); color: var(--text); font-family: 'Inter', system-ui, sans-serif; height: 100vh; display: flex; flex-direction: column; transition: background .3s, color .3s; }
41
+
42
+ /* ── BANNER ─── */
43
+ .banner-wrap { background: var(--bg2); border-bottom: 1px solid var(--border); padding: 14px 20px; display: flex; align-items: center; gap: 16px; box-shadow: var(--shadow); position: sticky; top: 0; z-index: 100; backdrop-filter: blur(8px); background: var(--header-bg); }
44
+ .banner-logo { font-family: 'JetBrains Mono', monospace; font-weight: 700; font-size: clamp(18px, 4vw, 28px); letter-spacing: 2px; background: linear-gradient(90deg, var(--accent), #7dd4fc, var(--accent)); -webkit-background-clip: text; -webkit-text-fill-color: transparent; background-clip: text; text-shadow: none; line-height: 1; user-select: none; }
45
+ .banner-eagle { font-size: 28px; filter: drop-shadow(0 0 8px var(--accent)); }
46
+ .banner-info { display: flex; flex-direction: column; gap: 2px; flex: 1; min-width: 0; }
47
+ .banner-sub { font-size: 11px; color: var(--text2); letter-spacing: 0.5px; font-family: 'JetBrains Mono', monospace; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
48
+ .banner-status { display: flex; align-items: center; gap: 6px; font-size: 12px; font-weight: 500; }
49
+ .dot { width: 8px; height: 8px; border-radius: 50%; background: #ef4444; flex-shrink: 0; }
50
+ .dot.connected { background: #22c55e; box-shadow: 0 0 6px #22c55e; animation: pulse-green 2s infinite; }
51
+ @keyframes pulse-green { 0%,100%{box-shadow:0 0 4px #22c55e} 50%{box-shadow:0 0 12px #22c55e} }
52
+ .banner-actions { display: flex; gap: 8px; align-items: center; flex-shrink: 0; }
53
+ .btn-icon { background: var(--bg3); border: 1px solid var(--border); color: var(--text2); width: 34px; height: 34px; border-radius: 8px; cursor: pointer; display: flex; align-items: center; justify-content: center; font-size: 16px; transition: all .2s; }
54
+ .btn-icon:hover { border-color: var(--accent); color: var(--accent); }
55
+
56
+ /* ── MESSAGES ─── */
57
+ #messages { flex: 1; overflow-y: auto; padding: 20px; display: flex; flex-direction: column; gap: 16px; scroll-behavior: smooth; }
58
+ #messages::-webkit-scrollbar { width: 4px; }
59
+ #messages::-webkit-scrollbar-track { background: transparent; }
60
+ #messages::-webkit-scrollbar-thumb { background: var(--border); border-radius: 2px; }
61
+ .msg-row { display: flex; gap: 10px; max-width: 800px; animation: fadeIn .25s ease; }
62
+ @keyframes fadeIn { from{opacity:0;transform:translateY(8px)} to{opacity:1;transform:none} }
63
+ .msg-row.user { flex-direction: row-reverse; align-self: flex-end; }
64
+ .msg-row.assistant { align-self: flex-start; }
65
+ .msg-avatar { width: 32px; height: 32px; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 16px; flex-shrink: 0; border: 1px solid var(--border); background: var(--bg3); }
66
+ .msg-bubble { max-width: min(70vw, 560px); padding: 12px 16px; border-radius: 16px; font-size: 14px; line-height: 1.6; }
67
+ .msg-row.user .msg-bubble { background: var(--user-bg); color: #fff; border-bottom-right-radius: 4px; }
68
+ .msg-row.assistant .msg-bubble { background: var(--asst-bg); border: 1px solid var(--border); border-bottom-left-radius: 4px; color: var(--text); }
69
+ .msg-bubble .prose h1,.msg-bubble .prose h2,.msg-bubble .prose h3 { color: var(--accent); margin: 8px 0 4px; font-size: 1em; font-weight: 600; }
70
+ .msg-bubble .prose p { margin: 4px 0; }
71
+ .msg-bubble .prose pre { background: var(--bg); border: 1px solid var(--border); padding: 10px 12px; border-radius: 8px; overflow-x: auto; margin: 8px 0; font-size: 12px; }
72
+ .msg-bubble .prose code { background: var(--bg); padding: 1px 5px; border-radius: 4px; font-size: 12px; font-family: 'JetBrains Mono', monospace; color: var(--accent); }
73
+ .msg-bubble .prose pre code { background: none; padding: 0; color: var(--text); }
74
+ .msg-bubble .prose ul,.msg-bubble .prose ol { padding-left: 18px; margin: 4px 0; }
75
+ .msg-bubble .prose a { color: var(--accent); text-decoration: underline; }
76
+ .msg-time { font-size: 10px; color: var(--text2); margin-top: 4px; text-align: right; }
77
+ .msg-row.assistant .msg-time { text-align: left; }
78
+
79
+ /* ── TYPING ─── */
80
+ .typing-dot { display: inline-block; width: 7px; height: 7px; border-radius: 50%; background: var(--accent); animation: blink 1.3s infinite; }
81
+ .typing-dot:nth-child(2){animation-delay:.2s}.typing-dot:nth-child(3){animation-delay:.4s}
82
+ @keyframes blink{0%,80%,100%{opacity:.2}40%{opacity:1}}
83
+
84
+ /* ── INPUT ─── */
85
+ .input-wrap { padding: 12px 20px 16px; border-top: 1px solid var(--border); background: var(--header-bg); backdrop-filter: blur(8px); }
86
+ .input-row { display: flex; gap: 10px; align-items: flex-end; max-width: 800px; margin: 0 auto; }
87
+ #input { flex: 1; background: var(--bg3); border: 1px solid var(--border); color: var(--text); border-radius: 14px; padding: 12px 16px; font-size: 14px; font-family: 'Inter', sans-serif; resize: none; min-height: 46px; max-height: 120px; outline: none; transition: border .2s, box-shadow .2s; line-height: 1.5; }
88
+ #input:focus { border-color: var(--accent); box-shadow: 0 0 0 2px color-mix(in srgb, var(--accent) 15%, transparent); }
89
+ #input::placeholder { color: var(--text2); }
90
+ #send-btn { width: 46px; height: 46px; border-radius: 14px; background: var(--accent); border: none; color: #fff; cursor: pointer; display: flex; align-items: center; justify-content: center; font-size: 18px; transition: all .2s; flex-shrink: 0; }
91
+ #send-btn:hover:not(:disabled) { background: var(--accent2); transform: scale(1.05); }
92
+ #send-btn:disabled { opacity: 0.4; cursor: default; transform: none; }
93
+ .input-hint { font-size: 11px; color: var(--text2); text-align: center; margin-top: 6px; }
94
+
95
+ /* ── WELCOME ─── */
96
+ .welcome { display: flex; flex-direction: column; align-items: center; justify-content: center; flex: 1; gap: 12px; text-align: center; padding: 32px; }
97
+ .welcome-logo { font-family: 'JetBrains Mono', monospace; font-size: clamp(32px, 8vw, 64px); font-weight: 700; letter-spacing: 4px; background: linear-gradient(135deg, #7dd4fc, var(--accent), #0077b6); -webkit-background-clip: text; -webkit-text-fill-color: transparent; background-clip: text; line-height: 1; }
98
+ .welcome-sub { font-size: 14px; color: var(--text2); max-width: 380px; line-height: 1.6; }
99
+ .welcome-chips { display: flex; flex-wrap: wrap; gap: 8px; justify-content: center; margin-top: 4px; }
100
+ .chip { background: var(--bg3); border: 1px solid var(--border); color: var(--text2); padding: 7px 14px; border-radius: 20px; font-size: 12px; cursor: pointer; transition: all .2s; }
101
+ .chip:hover { border-color: var(--accent); color: var(--accent); }
102
+ </style>
103
+ </head>
104
+ <body>
105
+
106
+ <!-- HEADER BANNER -->
107
+ <header class="banner-wrap">
108
+ <span class="banner-eagle">🦅</span>
109
+ <div style="display:flex;flex-direction:column;gap:1px">
110
+ <div class="banner-logo">HYPERCLAW</div>
111
+ <div class="banner-sub" id="agent-sub">AI Gateway Platform · Connecting…</div>
112
+ </div>
113
+ <div style="flex:1"></div>
114
+ <div class="banner-status">
115
+ <span class="dot" id="ws-dot"></span>
116
+ <span id="ws-label" style="color:var(--text2);font-size:13px">Offline</span>
117
+ </div>
118
+ <div class="banner-actions">
119
+ <button class="btn-icon" id="theme-btn" title="Toggle theme">🌙</button>
120
+ <button class="btn-icon" id="clear-btn" title="Clear chat">🗑️</button>
121
+ </div>
122
+ </header>
123
+
124
+ <!-- MESSAGES -->
125
+ <main id="messages">
126
+ <div class="welcome" id="welcome">
127
+ <div class="welcome-logo">HYPERCLAW</div>
128
+ <p class="welcome-sub">Your personal AI assistant — running on your hardware. Ask anything or pick a suggestion:</p>
129
+ <div class="welcome-chips">
130
+ <span class="chip" onclick="fillInput(this)">What can you do?</span>
131
+ <span class="chip" onclick="fillInput(this)">What's the weather today?</span>
132
+ <span class="chip" onclick="fillInput(this)">Search the web for AI news</span>
133
+ <span class="chip" onclick="fillInput(this)">Help me write a script</span>
134
+ <span class="chip" onclick="fillInput(this)">Show system status</span>
135
+ </div>
136
+ </div>
137
+ </main>
138
+
139
+ <!-- INPUT -->
140
+ <div class="input-wrap">
141
+ <form class="input-row" id="form">
142
+ <textarea id="input" rows="1" placeholder="Message HyperClaw… (Enter to send, Shift+Enter for new line)" autocomplete="off"></textarea>
143
+ <button type="submit" id="send-btn">↑</button>
144
+ </form>
145
+ <div class="input-hint">🦅 Hyper · <span id="model-hint">—</span></div>
146
+ </div>
147
+
148
+ <script>
149
+ const port = location.port || 18789;
150
+ const wsUrl = `ws://${location.hostname}:${port}`;
151
+ const apiUrl = `http://${location.hostname}:${port}`;
152
+
153
+ let ws = null, isStreaming = false, currentBubble = null;
154
+ const messagesEl = document.getElementById('messages');
155
+ const welcomeEl = document.getElementById('welcome');
156
+ const wsDot = document.getElementById('ws-dot');
157
+ const wsLabel = document.getElementById('ws-label');
158
+ const agentSub = document.getElementById('agent-sub');
159
+ const modelHint = document.getElementById('model-hint');
160
+ const input = document.getElementById('input');
161
+ const sendBtn = document.getElementById('send-btn');
162
+ const form = document.getElementById('form');
163
+
164
+ // ── Theme ────────────────────────────────────────────
165
+ const themeBtn = document.getElementById('theme-btn');
166
+ let dark = true;
167
+ themeBtn.onclick = () => {
168
+ dark = !dark;
169
+ document.documentElement.setAttribute('data-theme', dark ? 'dark' : 'light');
170
+ themeBtn.textContent = dark ? '🌙' : '☀️';
171
+ };
172
+
173
+ // ── Clear ────────────────────────────────────────────
174
+ document.getElementById('clear-btn').onclick = () => {
175
+ messagesEl.querySelectorAll('.msg-row').forEach(e => e.remove());
176
+ welcomeEl.style.display = 'flex';
177
+ };
178
+
179
+ // ── Chips ────────────────────────────────────────────
180
+ function fillInput(el) {
181
+ input.value = el.textContent;
182
+ input.focus();
183
+ autoResize();
184
+ }
185
+
186
+ // ── WebSocket ────────────────────────────────────────
187
+ function setConnected(ok) {
188
+ wsDot.className = 'dot' + (ok ? ' connected' : '');
189
+ wsLabel.textContent = ok ? 'Connected' : 'Reconnecting…';
190
+ wsLabel.style.color = ok ? '#22c55e' : 'var(--text2)';
191
+ }
192
+
193
+ function connectWs() {
194
+ ws = new WebSocket(wsUrl);
195
+ ws.onopen = () => { setConnected(true); };
196
+ ws.onclose = () => { setConnected(false); setTimeout(connectWs, 3000); };
197
+ ws.onerror = () => setConnected(false);
198
+ ws.onmessage = (e) => {
199
+ try {
200
+ const msg = JSON.parse(e.data);
201
+ if (msg.type === 'connect.ok' || msg.type === 'auth.ok') {
202
+ // connected
203
+ } else if (msg.type === 'chat:chunk') {
204
+ if (!currentBubble) {
205
+ removeTyping();
206
+ currentBubble = addMsg('assistant', '', true);
207
+ }
208
+ currentBubble._raw = (currentBubble._raw || '') + (msg.content || '');
209
+ currentBubble.textContent = currentBubble._raw;
210
+ messagesEl.scrollTop = messagesEl.scrollHeight;
211
+ } else if (msg.type === 'chat:response') {
212
+ removeTyping();
213
+ if (currentBubble) {
214
+ currentBubble.innerHTML = '<div class="prose">' + marked.parse(currentBubble._raw || msg.content || '') + '</div>';
215
+ currentBubble = null;
216
+ } else {
217
+ addMsg('assistant', msg.content || '');
218
+ }
219
+ addTimestamp();
220
+ finishStreaming();
221
+ } else if (msg.type === 'error') {
222
+ removeTyping();
223
+ addMsg('assistant', '⚠️ ' + (msg.message || 'Unknown error'));
224
+ currentBubble = null;
225
+ finishStreaming();
226
+ }
227
+ } catch (_) {}
228
+ };
229
+ }
230
+
231
+ function finishStreaming() {
232
+ isStreaming = false;
233
+ sendBtn.disabled = false;
234
+ currentBubble = null;
235
+ messagesEl.scrollTop = messagesEl.scrollHeight;
236
+ }
237
+
238
+ // ── Message helpers ───────────────────────────────────
239
+ function now() {
240
+ return new Date().toLocaleTimeString([], {hour:'2-digit',minute:'2-digit'});
241
+ }
242
+ let lastTimestampEl = null;
243
+
244
+ function addMsg(role, content, streaming = false) {
245
+ welcomeEl.style.display = 'none';
246
+ const row = document.createElement('div');
247
+ row.className = 'msg-row ' + role;
248
+ const avatar = document.createElement('div');
249
+ avatar.className = 'msg-avatar';
250
+ avatar.textContent = role === 'user' ? '👤' : '🦅';
251
+ const bubble = document.createElement('div');
252
+ bubble.className = 'msg-bubble';
253
+ if (!streaming && content) {
254
+ bubble.innerHTML = '<div class="prose">' + (role === 'assistant' ? marked.parse(content) : escHtml(content)) + '</div>';
255
+ } else {
256
+ bubble.textContent = content || '';
257
+ }
258
+ row.appendChild(avatar);
259
+ row.appendChild(bubble);
260
+ messagesEl.appendChild(row);
261
+ messagesEl.scrollTop = messagesEl.scrollHeight;
262
+ return bubble;
263
+ }
264
+
265
+ function addTimestamp() {
266
+ const t = document.createElement('div');
267
+ t.className = 'msg-time';
268
+ t.textContent = now();
269
+ const last = messagesEl.querySelector('.msg-row.assistant:last-child');
270
+ if (last) last.appendChild(t);
271
+ }
272
+
273
+ function addTyping() {
274
+ welcomeEl.style.display = 'none';
275
+ const row = document.createElement('div');
276
+ row.className = 'msg-row assistant';
277
+ row.id = 'typing-row';
278
+ row.innerHTML = '<div class="msg-avatar">🦅</div><div class="msg-bubble" style="padding:14px 18px"><span class="typing-dot"></span><span class="typing-dot"></span><span class="typing-dot"></span></div>';
279
+ messagesEl.appendChild(row);
280
+ messagesEl.scrollTop = messagesEl.scrollHeight;
281
+ }
282
+
283
+ function removeTyping() {
284
+ document.getElementById('typing-row')?.remove();
285
+ }
286
+
287
+ function escHtml(s) { return s.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;'); }
288
+
289
+ // ── Send ─────────────────────────────────────────────
290
+ form.onsubmit = async (e) => {
291
+ e.preventDefault();
292
+ const msg = input.value.trim();
293
+ if (!msg || isStreaming) return;
294
+ addMsg('user', msg);
295
+ input.value = '';
296
+ input.style.height = 'auto';
297
+ sendBtn.disabled = true;
298
+ isStreaming = true;
299
+ addTyping();
300
+
301
+ if (ws && ws.readyState === WebSocket.OPEN) {
302
+ ws.send(JSON.stringify({ type: 'chat:message', content: msg, source: 'webchat' }));
303
+ } else {
304
+ // REST fallback
305
+ try {
306
+ const res = await fetch(apiUrl + '/api/chat', {
307
+ method: 'POST', headers: {'Content-Type':'application/json'},
308
+ body: JSON.stringify({message: msg})
309
+ });
310
+ const data = await res.json();
311
+ removeTyping();
312
+ addMsg('assistant', data.response || data.error || '(no response)');
313
+ addTimestamp();
314
+ } catch (err) {
315
+ removeTyping();
316
+ addMsg('assistant', '⚠️ Could not reach gateway: ' + err.message);
317
+ }
318
+ finishStreaming();
319
+ }
320
+ };
321
+
322
+ // ── Textarea auto-resize ──────────────────────────────
323
+ function autoResize() {
324
+ input.style.height = 'auto';
325
+ input.style.height = Math.min(input.scrollHeight, 120) + 'px';
326
+ }
327
+ input.addEventListener('input', autoResize);
328
+ input.addEventListener('keydown', (e) => {
329
+ if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); form.dispatchEvent(new Event('submit')); }
330
+ });
331
+
332
+ // ── Init ─────────────────────────────────────────────
333
+ connectWs();
334
+ fetch(apiUrl + '/api/status').then(r => r.json()).then(d => {
335
+ if (d.agentName) agentSub.textContent = `${d.agentName} · ${d.model || '—'} · port ${d.port || port}`;
336
+ if (d.model) modelHint.textContent = d.model;
337
+ }).catch(() => {});
338
+ </script>
339
+ </body>
340
+ </html>
@@ -0,0 +1,146 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en" data-theme="dark">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>HyperClaw Dashboard</title>
7
+ <script src="https://cdn.tailwindcss.com"></script>
8
+ <link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;700&family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
9
+ <style>
10
+ :root {
11
+ --bg:#090e1a;--bg2:#0d1526;--bg3:#111d33;--border:#1e2d4a;
12
+ --accent:#00c2ff;--accent2:#0077b6;--text:#e2eaf8;--text2:#8ba0c0;
13
+ --card:#0d1829;--shadow:0 4px 24px rgba(0,194,255,0.07);
14
+ }
15
+ [data-theme="light"] {
16
+ --bg:#eef4ff;--bg2:#dde8ff;--bg3:#ffffff;--border:#b8cdf0;
17
+ --accent:#0077b6;--accent2:#005f99;--text:#0d1d3a;--text2:#3a5580;
18
+ --card:#ffffff;--shadow:0 4px 24px rgba(0,119,182,0.10);
19
+ }
20
+ *{box-sizing:border-box;margin:0;padding:0}
21
+ body{background:var(--bg);color:var(--text);font-family:'Inter',sans-serif;min-height:100vh}
22
+ .header{background:rgba(9,14,26,.95);backdrop-filter:blur(8px);border-bottom:1px solid var(--border);padding:14px 24px;display:flex;align-items:center;gap:14px;position:sticky;top:0;z-index:100}
23
+ [data-theme="light"] .header{background:rgba(238,244,255,.97)}
24
+ .logo{font-family:'JetBrains Mono',monospace;font-weight:700;font-size:22px;letter-spacing:2px;background:linear-gradient(90deg,var(--accent),#7dd4fc,var(--accent));-webkit-background-clip:text;-webkit-text-fill-color:transparent;background-clip:text}
25
+ .btn-icon{background:var(--bg3);border:1px solid var(--border);color:var(--text2);width:34px;height:34px;border-radius:8px;cursor:pointer;display:flex;align-items:center;justify-content:center;font-size:15px;transition:all .2s;margin-left:auto}
26
+ .btn-icon:hover{border-color:var(--accent);color:var(--accent)}
27
+ .main{max-width:960px;margin:0 auto;padding:28px 20px;display:grid;grid-template-columns:repeat(auto-fit,minmax(280px,1fr));gap:20px}
28
+ .card{background:var(--card);border:1px solid var(--border);border-radius:16px;padding:20px;box-shadow:var(--shadow)}
29
+ .card-title{font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:1.5px;color:var(--text2);margin-bottom:14px;display:flex;align-items:center;gap:8px}
30
+ .stat-row{display:flex;justify-content:space-between;align-items:center;padding:8px 0;border-bottom:1px solid var(--border);font-size:13px}
31
+ .stat-row:last-child{border:none}
32
+ .stat-label{color:var(--text2)}
33
+ .stat-val{font-weight:600;font-family:'JetBrains Mono',monospace;font-size:12px}
34
+ .badge{padding:3px 10px;border-radius:20px;font-size:11px;font-weight:600}
35
+ .badge-green{background:rgba(34,197,94,.15);color:#22c55e}
36
+ .badge-red{background:rgba(239,68,68,.15);color:#ef4444}
37
+ .badge-blue{background:rgba(0,194,255,.12);color:var(--accent)}
38
+ .link-row{display:flex;align-items:center;gap:10px;padding:10px 14px;border-radius:10px;border:1px solid var(--border);text-decoration:none;color:var(--text);font-size:13px;transition:all .2s;margin-bottom:8px}
39
+ .link-row:hover{border-color:var(--accent);color:var(--accent);background:rgba(0,194,255,.05)}
40
+ .pulse{width:8px;height:8px;border-radius:50%;background:#22c55e;box-shadow:0 0 6px #22c55e;animation:p 2s infinite;flex-shrink:0}
41
+ .pulse.off{background:#ef4444;box-shadow:0 0 6px #ef4444;animation:none}
42
+ @keyframes p{0%,100%{box-shadow:0 0 4px #22c55e}50%{box-shadow:0 0 12px #22c55e}}
43
+ .hero{grid-column:1/-1;background:var(--card);border:1px solid var(--border);border-radius:16px;padding:24px 28px;display:flex;align-items:center;gap:20px;box-shadow:var(--shadow)}
44
+ .hero-logo{font-family:'JetBrains Mono',monospace;font-weight:700;font-size:clamp(24px,5vw,44px);letter-spacing:3px;background:linear-gradient(135deg,#7dd4fc,var(--accent),var(--accent2));-webkit-background-clip:text;-webkit-text-fill-color:transparent;background-clip:text;line-height:1}
45
+ .hero-sub{font-size:13px;color:var(--text2);margin-top:6px}
46
+ </style>
47
+ </head>
48
+ <body>
49
+
50
+ <div class="header">
51
+ <span style="font-size:26px;filter:drop-shadow(0 0 8px var(--accent))">🦅</span>
52
+ <span class="logo">HYPERCLAW</span>
53
+ <span style="font-size:12px;color:var(--text2);margin-left:4px">Dashboard</span>
54
+ <button class="btn-icon" id="theme-btn" title="Toggle theme">🌙</button>
55
+ </div>
56
+
57
+ <div class="main">
58
+ <!-- Hero -->
59
+ <div class="hero">
60
+ <div>
61
+ <div class="hero-logo">HYPERCLAW</div>
62
+ <div class="hero-sub">AI Gateway Platform &nbsp;·&nbsp; <span id="hero-model">—</span> &nbsp;·&nbsp; Agent: <span id="hero-agent">—</span></div>
63
+ </div>
64
+ <div style="margin-left:auto;display:flex;gap:8px;align-items:center">
65
+ <div class="pulse off" id="hero-pulse"></div>
66
+ <span id="hero-status" style="font-size:13px;color:var(--text2)">Connecting…</span>
67
+ </div>
68
+ </div>
69
+
70
+ <!-- Gateway status -->
71
+ <div class="card">
72
+ <div class="card-title">⚡ Gateway</div>
73
+ <div class="stat-row"><span class="stat-label">Status</span><span class="stat-val" id="gw-running"><span class="badge badge-red">Stopped</span></span></div>
74
+ <div class="stat-row"><span class="stat-label">Port</span><span class="stat-val" id="gw-port">—</span></div>
75
+ <div class="stat-row"><span class="stat-label">Uptime</span><span class="stat-val" id="gw-uptime">—</span></div>
76
+ <div class="stat-row"><span class="stat-label">Sessions</span><span class="stat-val" id="gw-sessions">—</span></div>
77
+ <div class="stat-row"><span class="stat-label">Channels</span><span class="stat-val" id="gw-channels">—</span></div>
78
+ </div>
79
+
80
+ <!-- AI info -->
81
+ <div class="card">
82
+ <div class="card-title">🤖 Agent</div>
83
+ <div class="stat-row"><span class="stat-label">Name</span><span class="stat-val" id="ai-agent">—</span></div>
84
+ <div class="stat-row"><span class="stat-label">Model</span><span class="stat-val" id="ai-model">—</span></div>
85
+ <div class="stat-row"><span class="stat-label">Provider</span><span class="stat-val" id="ai-provider">—</span></div>
86
+ <div class="stat-row"><span class="stat-label">Version</span><span class="stat-val" id="ai-version">—</span></div>
87
+ </div>
88
+
89
+ <!-- Quick links -->
90
+ <div class="card">
91
+ <div class="card-title">🔗 Quick links</div>
92
+ <a class="link-row" href="/chat"><span>💬</span><span>Web Chat</span><span style="margin-left:auto;font-size:11px;color:var(--text2)">/chat</span></a>
93
+ <a class="link-row" href="/api/status" target="_blank"><span>📊</span><span>API Status (JSON)</span><span style="margin-left:auto;font-size:11px;color:var(--text2)">/api/status</span></a>
94
+ <a class="link-row" href="/api/canvas/state" target="_blank"><span>🎨</span><span>Canvas State</span><span style="margin-left:auto;font-size:11px;color:var(--text2)">/api/canvas/state</span></a>
95
+ </div>
96
+
97
+ <!-- Commands -->
98
+ <div class="card">
99
+ <div class="card-title">⌨️ CLI commands</div>
100
+ <div class="stat-row"><span class="stat-label" style="font-family:'JetBrains Mono',monospace;font-size:11px">hyperclaw chat</span><span class="badge badge-blue">Terminal</span></div>
101
+ <div class="stat-row"><span class="stat-label" style="font-family:'JetBrains Mono',monospace;font-size:11px">hyperclaw status</span><span class="badge badge-blue">Status</span></div>
102
+ <div class="stat-row"><span class="stat-label" style="font-family:'JetBrains Mono',monospace;font-size:11px">hyperclaw health</span><span class="badge badge-blue">Health</span></div>
103
+ <div class="stat-row"><span class="stat-label" style="font-family:'JetBrains Mono',monospace;font-size:11px">hyperclaw osint</span><span class="badge badge-blue">OSINT</span></div>
104
+ <div class="stat-row"><span class="stat-label" style="font-family:'JetBrains Mono',monospace;font-size:11px">hyperclaw hub</span><span class="badge badge-blue">Skills</span></div>
105
+ </div>
106
+ </div>
107
+
108
+ <script>
109
+ const apiUrl = `http://${location.hostname}:${location.port || 18789}`;
110
+ const themeBtn = document.getElementById('theme-btn');
111
+ let dark = true;
112
+ themeBtn.onclick = () => {
113
+ dark = !dark;
114
+ document.documentElement.setAttribute('data-theme', dark ? 'dark' : 'light');
115
+ themeBtn.textContent = dark ? '🌙' : '☀️';
116
+ };
117
+
118
+ function load() {
119
+ fetch(apiUrl + '/api/status').then(r => r.json()).then(d => {
120
+ const running = d.running;
121
+ document.getElementById('gw-running').innerHTML = running
122
+ ? '<span class="badge badge-green">Running</span>'
123
+ : '<span class="badge badge-red">Stopped</span>';
124
+ document.getElementById('gw-port').textContent = d.port || '—';
125
+ document.getElementById('gw-uptime').textContent = d.uptime || '—';
126
+ document.getElementById('gw-sessions').textContent = d.sessions ?? '—';
127
+ document.getElementById('gw-channels').textContent = (d.channels?.length || d.channels) ?? '0';
128
+ document.getElementById('ai-agent').textContent = d.agentName || '—';
129
+ document.getElementById('ai-model').textContent = d.model || '—';
130
+ document.getElementById('ai-provider').textContent = d.provider || '—';
131
+ document.getElementById('ai-version').textContent = d.version || '—';
132
+ document.getElementById('hero-model').textContent = d.model || '—';
133
+ document.getElementById('hero-agent').textContent = d.agentName || '—';
134
+ const pulse = document.getElementById('hero-pulse');
135
+ const status = document.getElementById('hero-status');
136
+ if (running) { pulse.classList.remove('off'); status.textContent = 'Running'; status.style.color = '#22c55e'; }
137
+ else { pulse.classList.add('off'); status.textContent = 'Stopped'; status.style.color = '#ef4444'; }
138
+ }).catch(() => {
139
+ document.getElementById('hero-status').textContent = 'Unreachable';
140
+ });
141
+ }
142
+ load();
143
+ setInterval(load, 5000);
144
+ </script>
145
+ </body>
146
+ </html>