hyperclaw 5.3.1 → 5.3.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/static/chat.html CHANGED
@@ -99,6 +99,22 @@
99
99
  .welcome-chips { display: flex; flex-wrap: wrap; gap: 8px; justify-content: center; margin-top: 4px; }
100
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
101
  .chip:hover { border-color: var(--accent); color: var(--accent); }
102
+
103
+ /* ── LOCAL TERMINAL ─── */
104
+ .terminal-wrap { border-top: 1px solid var(--border); background: rgba(0,0,0,0.4); flex-shrink: 0; }
105
+ .terminal-toggle { width: 100%; display: flex; align-items: center; justify-content: space-between; padding: 8px 16px; font-size: 12px; color: var(--text2); background: transparent; border: none; cursor: pointer; transition: background .2s; }
106
+ .terminal-toggle:hover { background: var(--bg3); }
107
+ .terminal-body { padding: 12px 16px; display: none; }
108
+ .terminal-body.open { display: block; }
109
+ .terminal-btns { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 10px; }
110
+ .terminal-btn { padding: 6px 12px; font-size: 11px; border-radius: 6px; background: var(--bg3); border: 1px solid var(--border); color: var(--text); cursor: pointer; transition: all .2s; }
111
+ .terminal-btn:hover:not(:disabled) { border-color: var(--accent); color: var(--accent); }
112
+ .terminal-btn:disabled { opacity: 0.5; cursor: default; }
113
+ .terminal-log { background: rgba(0,0,0,0.7); border: 1px solid var(--border); border-radius: 8px; padding: 10px; height: 140px; overflow-y: auto; font-family: 'JetBrains Mono', monospace; font-size: 11px; color: var(--text2); white-space: pre-wrap; margin-bottom: 10px; }
114
+ .terminal-log:empty::before { content: 'No commands yet. Use the buttons above or type a command below.'; color: var(--text2); opacity: 0.7; }
115
+ .terminal-input-row { display: flex; gap: 8px; align-items: center; }
116
+ .terminal-input { flex: 1; background: var(--bg3); border: 1px solid var(--border); border-radius: 6px; padding: 8px 12px; font-size: 12px; font-family: 'JetBrains Mono', monospace; color: var(--text); outline: none; }
117
+ .terminal-input:focus { border-color: var(--accent); }
102
118
  </style>
103
119
  </head>
104
120
  <body>
@@ -115,9 +131,16 @@
115
131
  <span class="dot" id="ws-dot"></span>
116
132
  <span id="ws-label" style="color:var(--text2);font-size:13px">Offline</span>
117
133
  </div>
118
- <div class="banner-actions">
134
+ <div class="banner-actions" style="display:flex;align-items:center;gap:8px">
135
+ <select id="prompt-select" style="background:var(--bg3);border:1px solid var(--border);color:var(--text);border-radius:8px;padding:6px 10px;font-size:12px;cursor:pointer">
136
+ <option value="">General</option>
137
+ <option value="ethical-hacker">Ethical Hacker</option>
138
+ <option value="hyperclaw">HyperClaw Dev</option>
139
+ <option value="osint">OSINT</option>
140
+ </select>
141
+ <button class="btn-icon" id="new-chat-btn" title="New chat">💬</button>
142
+ <button class="btn-icon" id="clear-btn" title="Clear messages">🗑️</button>
119
143
  <button class="btn-icon" id="theme-btn" title="Toggle theme">🌙</button>
120
- <button class="btn-icon" id="clear-btn" title="Clear chat">🗑️</button>
121
144
  </div>
122
145
  </header>
123
146
 
@@ -145,6 +168,28 @@
145
168
  <div class="input-hint">🦅 HyperClaw · <span id="model-hint">—</span></div>
146
169
  </div>
147
170
 
171
+ <!-- LOCAL TERMINAL -->
172
+ <div class="terminal-wrap">
173
+ <button type="button" class="terminal-toggle" id="terminal-toggle">
174
+ <span>› Local terminal</span>
175
+ <span id="terminal-toggle-label">Show</span>
176
+ </button>
177
+ <div class="terminal-body" id="terminal-body">
178
+ <div class="terminal-btns">
179
+ <button type="button" class="terminal-btn" data-cmd="npm run build">Build</button>
180
+ <button type="button" class="terminal-btn" data-cmd="npm install">Install</button>
181
+ <button type="button" class="terminal-btn" data-cmd="npm test">Test</button>
182
+ <button type="button" class="terminal-btn" data-cmd="npx hyperclaw doctor --fix">Doctor</button>
183
+ <button type="button" class="terminal-btn" data-cmd="npx hyperclaw gateway status">Gateway status</button>
184
+ </div>
185
+ <div class="terminal-log" id="terminal-log"></div>
186
+ <div class="terminal-input-row">
187
+ <input type="text" class="terminal-input" id="terminal-input" placeholder="Run a command in your HyperClaw workspace..." />
188
+ <button type="button" class="terminal-btn" id="terminal-run">Run</button>
189
+ </div>
190
+ </div>
191
+ </div>
192
+
148
193
  <script>
149
194
  const port = location.port || 18789;
150
195
  const wsUrl = `ws://${location.hostname}:${port}`;
@@ -170,7 +215,17 @@
170
215
  themeBtn.textContent = dark ? '🌙' : '☀️';
171
216
  };
172
217
 
173
- // ── Clear ────────────────────────────────────────────
218
+ // ── New chat / Clear ─────────────────────────────────
219
+ const promptSelect = document.getElementById('prompt-select');
220
+ const PROMPT_PREFIX = {
221
+ 'ethical-hacker': 'Act as an ethical hacker / security researcher. Authorized testing only. ',
222
+ 'hyperclaw': 'You are helping with HyperClaw development. Be concise and code-focused. ',
223
+ 'osint': 'Act as an OSINT analyst. Passive reconnaissance, open-source research. '
224
+ };
225
+ document.getElementById('new-chat-btn').onclick = () => {
226
+ messagesEl.querySelectorAll('.msg-row').forEach(e => e.remove());
227
+ welcomeEl.style.display = 'flex';
228
+ };
174
229
  document.getElementById('clear-btn').onclick = () => {
175
230
  messagesEl.querySelectorAll('.msg-row').forEach(e => e.remove());
176
231
  welcomeEl.style.display = 'flex';
@@ -297,6 +352,9 @@
297
352
  e.preventDefault();
298
353
  const msg = input.value.trim();
299
354
  if (!msg || isStreaming) return;
355
+ const preset = promptSelect?.value || '';
356
+ const prefix = PROMPT_PREFIX[preset] || '';
357
+ const fullMsg = prefix ? prefix + msg : msg;
300
358
  addMsg('user', msg);
301
359
  input.value = '';
302
360
  input.style.height = 'auto';
@@ -305,13 +363,13 @@
305
363
  addTyping();
306
364
 
307
365
  if (ws && ws.readyState === WebSocket.OPEN) {
308
- ws.send(JSON.stringify({ type: 'chat:message', content: msg, source: 'webchat' }));
366
+ ws.send(JSON.stringify({ type: 'chat:message', content: fullMsg, source: 'webchat' }));
309
367
  } else {
310
368
  // REST fallback
311
369
  try {
312
370
  const res = await fetch(apiUrl + '/api/chat', {
313
371
  method: 'POST', headers: {'Content-Type':'application/json'},
314
- body: JSON.stringify({message: msg})
372
+ body: JSON.stringify({message: fullMsg})
315
373
  });
316
374
  const data = await res.json();
317
375
  removeTyping();
@@ -335,6 +393,55 @@
335
393
  if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); form.dispatchEvent(new Event('submit')); }
336
394
  });
337
395
 
396
+ // ── Local Terminal ───────────────────────────────────
397
+ const terminalToggle = document.getElementById('terminal-toggle');
398
+ const terminalBody = document.getElementById('terminal-body');
399
+ const terminalLog = document.getElementById('terminal-log');
400
+ const terminalInput = document.getElementById('terminal-input');
401
+ const terminalRun = document.getElementById('terminal-run');
402
+ const toggleLabel = document.getElementById('terminal-toggle-label');
403
+ let terminalRunning = false;
404
+
405
+ terminalToggle.onclick = () => {
406
+ terminalBody.classList.toggle('open', !terminalBody.classList.contains('open'));
407
+ toggleLabel.textContent = terminalBody.classList.contains('open') ? 'Hide' : 'Show';
408
+ };
409
+
410
+ async function runTerminalCmd(cmd) {
411
+ cmd = (cmd || '').trim();
412
+ if (!cmd || terminalRunning) return;
413
+ terminalLog.append('$ ' + cmd + '\n');
414
+ terminalLog.scrollTop = terminalLog.scrollHeight;
415
+ terminalRunning = true;
416
+ terminalRun.disabled = true;
417
+ document.querySelectorAll('.terminal-btn[data-cmd]').forEach(b => b.disabled = true);
418
+ try {
419
+ const res = await fetch(apiUrl + '/api/terminal', {
420
+ method: 'POST', headers: {'Content-Type': 'application/json'},
421
+ body: JSON.stringify({command: cmd})
422
+ });
423
+ const data = await res.json();
424
+ if (data.stdout) terminalLog.append(data.stdout.trimEnd() + '\n');
425
+ if (data.stderr) terminalLog.append('! ' + data.stderr.trimEnd() + '\n');
426
+ terminalLog.append('(exit code ' + (data.code ?? 0) + ')\n');
427
+ } catch (e) {
428
+ terminalLog.append('Error: ' + (e.message || String(e)) + '\n');
429
+ } finally {
430
+ terminalRunning = false;
431
+ terminalRun.disabled = false;
432
+ document.querySelectorAll('.terminal-btn[data-cmd]').forEach(b => b.disabled = false);
433
+ terminalLog.scrollTop = terminalLog.scrollHeight;
434
+ }
435
+ }
436
+
437
+ document.querySelectorAll('.terminal-btn[data-cmd]').forEach(btn => {
438
+ btn.onclick = () => runTerminalCmd(btn.dataset.cmd);
439
+ });
440
+ terminalRun.onclick = () => { runTerminalCmd(terminalInput.value); terminalInput.value = ''; };
441
+ terminalInput.onkeydown = (e) => {
442
+ if (e.key === 'Enter') { runTerminalCmd(terminalInput.value); terminalInput.value = ''; }
443
+ };
444
+
338
445
  // ── Init ─────────────────────────────────────────────
339
446
  connectWs();
340
447
  fetch(apiUrl + '/api/status').then(r => r.json()).then(d => {