hyperclaw 5.3.1 → 5.3.3

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 (48) hide show
  1. package/README.md +2 -0
  2. package/dist/banner-B8OcbGCR.js +143 -0
  3. package/dist/banner-BIOxqoND.js +7 -0
  4. package/dist/banner-BT9llPVP.js +7 -0
  5. package/dist/banner-DNSYi9N-.js +143 -0
  6. package/dist/chat-CKLXvJjd.js +545 -0
  7. package/dist/chat-CvZyAfqV.js +545 -0
  8. package/dist/daemon-Bi0rcsh5.js +421 -0
  9. package/dist/daemon-DL5zfvZd.js +7 -0
  10. package/dist/daemon-DkK4j69t.js +421 -0
  11. package/dist/daemon-DnWIgRUn.js +7 -0
  12. package/dist/engine-BZ44Vuhh.js +7 -0
  13. package/dist/engine-C1osvhmq.js +327 -0
  14. package/dist/engine-EA8vevRJ.js +327 -0
  15. package/dist/engine-SjHViH0c.js +7 -0
  16. package/dist/hyperclawbot-CyIGak-G.js +516 -0
  17. package/dist/hyperclawbot-DC3jGsoA.js +516 -0
  18. package/dist/mcp-loader-BBlqPFQl.js +93 -0
  19. package/dist/mcp-loader-DyklUhL2.js +93 -0
  20. package/dist/onboard-BriaGw6G.js +3812 -0
  21. package/dist/onboard-Bu3xDVur.js +14 -0
  22. package/dist/onboard-DRgSPOuY.js +3812 -0
  23. package/dist/onboard-qPUG8PBU.js +14 -0
  24. package/dist/orchestrator-BURpEF0U.js +6 -0
  25. package/dist/orchestrator-C0_UaVsn.js +6 -0
  26. package/dist/orchestrator-CnEDItE1.js +189 -0
  27. package/dist/orchestrator-CqXGMcxd.js +189 -0
  28. package/dist/osint-BC5eNzVi.js +283 -0
  29. package/dist/osint-BiZZ_d-g.js +283 -0
  30. package/dist/osint-chat-DaRWDl3X.js +789 -0
  31. package/dist/osint-chat-myilxKVw.js +789 -0
  32. package/dist/run-main.js +128 -45
  33. package/dist/server-B5wYaa-U.js +4 -0
  34. package/dist/server-DBXhcGiq.js +4 -0
  35. package/dist/server-O4Zv8s74.js +1366 -0
  36. package/dist/server-viuRgDlF.js +1365 -0
  37. package/dist/skill-runtime-B7U9JxNS.js +5 -0
  38. package/dist/skill-runtime-DXKqnJos.js +104 -0
  39. package/dist/skill-runtime-IFk6whV0.js +104 -0
  40. package/dist/skill-runtime-vylCVHmD.js +5 -0
  41. package/dist/src-BA04v4CG.js +458 -0
  42. package/dist/src-BWpeRe8J.js +63 -0
  43. package/dist/src-Czb6-Nkc.js +458 -0
  44. package/dist/src-DSZTfNdO.js +63 -0
  45. package/dist/sub-agent-tools-Dzh0nVzy.js +39 -0
  46. package/dist/sub-agent-tools-zyfsZ8k0.js +39 -0
  47. package/package.json +1 -1
  48. package/static/chat.html +581 -163
package/static/chat.html CHANGED
@@ -1,5 +1,5 @@
1
1
  <!DOCTYPE html>
2
- <html lang="en" data-theme="dark">
2
+ <html lang="en">
3
3
  <head>
4
4
  <meta charset="UTF-8">
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
@@ -8,141 +8,366 @@
8
8
  <script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
9
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
10
  <style>
11
+ /* ── ACCENT THEME VARS ── */
11
12
  :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);
13
+ --accent: #22d3ee;
14
+ --accent2: #06b6d4;
15
+ --accent-glow: rgba(34,211,238,0.3);
16
+ --accent-bg: rgba(34,211,238,0.08);
24
17
  }
25
18
  [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);
19
+ --accent: #0891b2;
20
+ --accent2: #0e7490;
21
+ --accent-glow: rgba(8,145,178,0.25);
22
+ --accent-bg: rgba(8,145,178,0.06);
38
23
  }
24
+ [data-daemon="true"] {
25
+ --accent: #f87171;
26
+ --accent2: #ef4444;
27
+ --accent-glow: rgba(248,113,113,0.3);
28
+ --accent-bg: rgba(248,113,113,0.08);
29
+ }
30
+ [data-theme="light"][data-daemon="true"] {
31
+ --accent: #dc2626;
32
+ --accent2: #b91c1c;
33
+ --accent-glow: rgba(220,38,38,0.25);
34
+ --accent-bg: rgba(220,38,38,0.06);
35
+ }
36
+
39
37
  * { 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} }
38
+ body { background: #0a0a0a; color: #e5e5e5; font-family: 'Inter', system-ui, sans-serif; height: 100vh; display: flex; overflow: hidden; }
39
+
40
+ /* ── SCROLLBAR ── */
41
+ ::-webkit-scrollbar { width: 4px; }
42
+ ::-webkit-scrollbar-track { background: transparent; }
43
+ ::-webkit-scrollbar-thumb { background: #2a2a2a; border-radius: 2px; }
44
+
45
+ /* ── SIDEBAR ── */
46
+ #sidebar { width: 240px; flex-shrink: 0; background: #111111; border-right: 1px solid #1e1e1e; display: flex; flex-direction: column; height: 100vh; overflow: hidden; transition: width 0.2s; }
47
+ #sidebar.collapsed { width: 0; border: none; }
48
+
49
+ .sidebar-top { padding: 14px 12px 8px; flex-shrink: 0; }
50
+ .sidebar-logo { display: flex; align-items: center; gap: 9px; padding: 6px 8px; margin-bottom: 6px; }
51
+ .sidebar-logo img { width: 26px; height: 26px; border-radius: 6px; }
52
+ .sidebar-logo-text { font-size: 13px; font-weight: 600; color: #e5e5e5; }
53
+
54
+ .sidebar-btn { width: 100%; display: flex; align-items: center; gap: 9px; padding: 8px 10px; border-radius: 8px; border: none; background: transparent; color: #a3a3a3; font-size: 13px; cursor: pointer; transition: background .15s, color .15s; text-align: left; white-space: nowrap; overflow: hidden; }
55
+ .sidebar-btn:hover { background: #1e1e1e; color: #e5e5e5; }
56
+ .sidebar-btn.active { background: #1e1e1e; color: #e5e5e5; }
57
+ .sidebar-btn svg { flex-shrink: 0; }
58
+
59
+ .sidebar-new-chat { width: calc(100% - 24px); margin: 0 12px 4px; display: flex; align-items: center; gap: 8px; padding: 8px 12px; border-radius: 8px; border: 1px solid #2a2a2a; background: transparent; color: #e5e5e5; font-size: 13px; cursor: pointer; transition: background .15s, border-color .15s; font-family: inherit; }
60
+ .sidebar-new-chat:hover { background: #1e1e1e; border-color: #3a3a3a; }
61
+
62
+ .sidebar-search { margin: 4px 12px; position: relative; }
63
+ .sidebar-search input { width: 100%; background: #1a1a1a; border: 1px solid #2a2a2a; border-radius: 8px; padding: 7px 10px 7px 32px; font-size: 12px; color: #e5e5e5; outline: none; font-family: inherit; transition: border-color .15s; }
64
+ .sidebar-search input:focus { border-color: #3a3a3a; }
65
+ .sidebar-search input::placeholder { color: #555; }
66
+ .sidebar-search-icon { position: absolute; left: 10px; top: 50%; transform: translateY(-50%); color: #555; pointer-events: none; }
67
+
68
+ .sidebar-section { padding: 16px 12px 4px; font-size: 11px; font-weight: 500; color: #555; letter-spacing: 0.5px; text-transform: uppercase; }
69
+
70
+ .sidebar-project { width: calc(100% - 24px); margin: 0 12px 2px; display: flex; align-items: center; gap: 8px; padding: 7px 10px; border-radius: 8px; border: none; background: transparent; color: #a3a3a3; font-size: 12px; cursor: pointer; transition: background .15s, color .15s; text-align: left; font-family: inherit; overflow: hidden; }
71
+ .sidebar-project:hover { background: #1e1e1e; color: #e5e5e5; }
72
+ .sidebar-project.active { background: #1e1e1e; color: #e5e5e5; }
73
+ .sidebar-project-dot { width: 7px; height: 7px; border-radius: 50%; flex-shrink: 0; }
74
+
75
+ .sidebar-chat-item { width: calc(100% - 24px); margin: 0 12px 1px; display: flex; align-items: center; gap: 8px; padding: 6px 10px; border-radius: 8px; border: none; background: transparent; color: #777; font-size: 12px; cursor: pointer; transition: background .15s, color .15s; text-align: left; font-family: inherit; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
76
+ .sidebar-chat-item:hover { background: #1a1a1a; color: #ccc; }
77
+
78
+ .sidebar-history { flex: 1; overflow-y: auto; padding-bottom: 8px; }
79
+
80
+ .sidebar-bottom { padding: 8px 12px 12px; border-top: 1px solid #1e1e1e; flex-shrink: 0; }
81
+
82
+ /* ── MAIN ── */
83
+ #main { flex: 1; display: flex; flex-direction: column; min-width: 0; height: 100vh; background: #0a0a0a; }
84
+
85
+ /* ── HEADER ── */
86
+ .chat-header { display: flex; align-items: center; justify-content: space-between; padding: 10px 16px; border-bottom: 1px solid #1e1e1e; background: #0a0a0a; flex-shrink: 0; gap: 8px; }
87
+ .chat-header-left { display: flex; align-items: center; gap: 8px; }
88
+ .sidebar-toggle { background: transparent; border: none; color: #555; cursor: pointer; padding: 4px; border-radius: 6px; display: flex; transition: color .15s, background .15s; }
89
+ .sidebar-toggle:hover { color: #ccc; background: #1e1e1e; }
90
+ .chat-header-right { display: flex; align-items: center; gap: 6px; flex-shrink: 0; }
91
+ .header-status { display: flex; align-items: center; gap: 5px; font-size: 12px; color: #555; }
92
+ .dot { width: 6px; height: 6px; border-radius: 50%; background: #ef4444; flex-shrink: 0; }
93
+ .dot.connected { background: #22c55e; box-shadow: 0 0 5px #22c55e; animation: pulse-g 2s infinite; }
94
+ @keyframes pulse-g { 0%,100%{opacity:1} 50%{opacity:.6} }
95
+ .hdr-btn { background: transparent; border: 1px solid #2a2a2a; color: #777; padding: 5px 10px; border-radius: 7px; font-size: 11px; cursor: pointer; font-family: inherit; transition: background .15s, color .15s, border-color .15s; white-space: nowrap; }
96
+ .hdr-btn:hover { background: #1e1e1e; color: #ccc; border-color: #3a3a3a; }
97
+ .hdr-btn.active { background: #2a1a1a; border-color: #5a2a2a; color: #f87171; }
98
+ .hdr-select { background: #111; border: 1px solid #2a2a2a; color: #ccc; padding: 5px 8px; border-radius: 7px; font-size: 11px; cursor: pointer; font-family: inherit; outline: none; }
99
+
100
+ /* ── MESSAGES ── */
101
+ #messages { flex: 1; overflow-y: auto; padding: 24px 20px; display: flex; flex-direction: column; gap: 16px; scroll-behavior: smooth; min-height: 0; }
102
+ .msg-row { display: flex; gap: 10px; max-width: 720px; animation: fadeIn .2s ease; }
103
+ @keyframes fadeIn { from{opacity:0;transform:translateY(6px)} to{opacity:1;transform:none} }
63
104
  .msg-row.user { flex-direction: row-reverse; align-self: flex-end; }
64
105
  .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; }
106
+ .msg-avatar { width: 28px; height: 28px; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 14px; flex-shrink: 0; background: #1a1a1a; border: 1px solid #2a2a2a; margin-top: 2px; }
107
+ .msg-bubble { max-width: min(65vw, 560px); padding: 11px 15px; border-radius: 16px; font-size: 13.5px; line-height: 1.65; }
108
+ .msg-row.user .msg-bubble { background: #c0392b; color: #fff; border-bottom-right-radius: 4px; }
109
+ .msg-row.assistant .msg-bubble { background: #141414; border: 1px solid #222; border-bottom-left-radius: 4px; color: #ddd; }
110
+ .msg-bubble .prose h1,.msg-bubble .prose h2,.msg-bubble .prose h3 { color: #f87171; margin: 8px 0 4px; font-weight: 600; }
70
111
  .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); }
112
+ .msg-bubble .prose pre { background: #0d0d0d; border: 1px solid #222; padding: 10px 12px; border-radius: 8px; overflow-x: auto; margin: 8px 0; font-size: 12px; }
113
+ .msg-bubble .prose code { background: #0d0d0d; padding: 1px 5px; border-radius: 4px; font-size: 12px; font-family: 'JetBrains Mono', monospace; color: #f87171; }
114
+ .msg-bubble .prose pre code { background: none; padding: 0; color: #ccc; }
74
115
  .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; }
116
+ .msg-bubble .prose a { color: #60a5fa; text-decoration: underline; }
117
+ .msg-time { font-size: 10px; color: #444; margin-top: 4px; }
118
+ .msg-row.user .msg-time { text-align: right; }
78
119
 
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}
120
+ /* ── TYPING ── */
121
+ .typing-dot { display: inline-block; width: 6px; height: 6px; border-radius: 50%; background: #f87171; animation: blink 1.2s infinite; }
122
+ .typing-dot:nth-child(2){animation-delay:.18s}.typing-dot:nth-child(3){animation-delay:.36s}
82
123
  @keyframes blink{0%,80%,100%{opacity:.2}40%{opacity:1}}
83
124
 
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; }
125
+ /* ── BANNER ── */
126
+ .hc-banner { display: flex; flex-direction: column; align-items: center; padding: 24px 0 16px; flex-shrink: 0; pointer-events: none; user-select: none; opacity: 0.45; }
127
+ .hc-banner-text { font-family: 'JetBrains Mono', monospace; font-weight: 700; font-size: 2.4rem; letter-spacing: 0.3em; color: var(--accent); text-shadow: 0 0 32px var(--accent), 0 0 64px var(--accent-bg); line-height: 1; }
128
+ .hc-banner-sub { font-family: 'JetBrains Mono', monospace; font-size: 11px; color: var(--accent); letter-spacing: 0.25em; margin-top: 8px; opacity: 0.75; }
129
+
130
+ /* ── WELCOME ── */
131
+ .welcome { display: flex; flex-direction: column; align-items: center; justify-content: center; flex: 1; gap: 14px; text-align: center; padding: 40px; }
132
+ .welcome-eagle { font-size: 48px; margin-bottom: 4px; filter: drop-shadow(0 0 12px rgba(239,68,68,0.3)); }
133
+ .welcome-title { font-size: 26px; font-weight: 600; color: #e5e5e5; }
134
+ .welcome-sub { font-size: 13px; color: #666; max-width: 360px; line-height: 1.6; }
99
135
  .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); }
136
+ .chip { background: #141414; border: 1px solid #2a2a2a; color: #888; padding: 7px 14px; border-radius: 20px; font-size: 12px; cursor: pointer; transition: all .2s; font-family: inherit; }
137
+ .chip:hover { border-color: #555; color: #ccc; background: #1e1e1e; }
138
+
139
+ /* ── INPUT ── */
140
+ .input-wrap { padding: 12px 16px 14px; border-top: 1px solid #1a1a1a; background: #0a0a0a; flex-shrink: 0; }
141
+ .input-row { display: flex; gap: 8px; align-items: flex-end; max-width: 720px; margin: 0 auto; }
142
+ #input { flex: 1; background: #141414; border: 1px solid #2a2a2a; color: #e5e5e5; border-radius: 12px; padding: 11px 14px; font-size: 13.5px; font-family: 'Inter', sans-serif; resize: none; min-height: 44px; max-height: 130px; outline: none; transition: border-color .2s; line-height: 1.5; }
143
+ #input:focus { border-color: #3a3a3a; }
144
+ #input::placeholder { color: #444; }
145
+ #send-btn { width: 40px; height: 40px; border-radius: 10px; background: #c0392b; border: none; color: #fff; cursor: pointer; display: flex; align-items: center; justify-content: center; transition: all .2s; flex-shrink: 0; }
146
+ #send-btn:hover:not(:disabled) { background: #e74c3c; }
147
+ #send-btn:disabled { opacity: 0.3; cursor: default; }
148
+ .input-hint { font-size: 11px; color: #333; text-align: center; margin-top: 6px; max-width: 720px; margin-left: auto; margin-right: auto; }
149
+
150
+ /* ── TERMINAL PANEL ── */
151
+ #terminal-panel { border-top: 1px solid #1e1e1e; background: #0d0d0d; flex-shrink: 0; height: 220px; display: none; flex-direction: column; }
152
+ #terminal-panel.open { display: flex; }
153
+ .term-titlebar { display: flex; align-items: center; justify-content: space-between; padding: 6px 14px; background: #141414; border-bottom: 1px solid #1e1e1e; flex-shrink: 0; }
154
+ .term-dots { display: flex; gap: 5px; }
155
+ .term-dot { width: 11px; height: 11px; border-radius: 50%; }
156
+ .term-info { font-size: 11px; color: #444; font-family: 'JetBrains Mono', monospace; flex: 1; text-align: center; }
157
+ .term-titlebar-right { display: flex; align-items: center; gap: 10px; }
158
+ .term-clear-btn { font-size: 11px; color: #444; background: none; border: none; cursor: pointer; font-family: inherit; transition: color .15s; }
159
+ .term-clear-btn:hover { color: #888; }
160
+ .term-close-btn { background: none; border: none; color: #444; cursor: pointer; display: flex; transition: color .15s; }
161
+ .term-close-btn:hover { color: #ccc; }
162
+ .term-quick { display: flex; gap: 6px; padding: 6px 12px; border-bottom: 1px solid #191919; flex-shrink: 0; overflow-x: auto; }
163
+ .term-quick::-webkit-scrollbar { height: 0; }
164
+ .term-quick-btn { padding: 4px 10px; border-radius: 6px; background: #1a1a1a; border: 1px solid #2a2a2a; color: #777; font-size: 11px; cursor: pointer; font-family: 'JetBrains Mono', monospace; transition: all .15s; white-space: nowrap; flex-shrink: 0; }
165
+ .term-quick-btn:hover:not(:disabled) { background: #222; color: #ccc; border-color: #3a3a3a; }
166
+ .term-quick-btn:disabled { opacity: 0.4; cursor: default; }
167
+ #terminal-log { flex: 1; overflow-y: auto; padding: 8px 14px; font-family: 'JetBrains Mono', monospace; font-size: 11px; color: #888; white-space: pre-wrap; line-height: 1.5; min-height: 0; }
168
+ .term-log-cmd { color: #67e8f9; }
169
+ .term-log-err { color: #f87171; }
170
+ .term-input-row { display: flex; align-items: center; gap: 8px; padding: 6px 12px 8px; border-top: 1px solid #1a1a1a; background: #141414; flex-shrink: 0; }
171
+ .term-prompt { color: #67e8f9; font-family: 'JetBrains Mono', monospace; font-size: 12px; flex-shrink: 0; }
172
+ #terminal-input { flex: 1; background: transparent; border: none; color: #e5e5e5; font-family: 'JetBrains Mono', monospace; font-size: 12px; outline: none; }
173
+ #terminal-input::placeholder { color: #333; }
174
+ .term-spinner { width: 12px; height: 12px; border: 1.5px solid #d97706; border-top-color: transparent; border-radius: 50%; animation: spin .7s linear infinite; flex-shrink: 0; display: none; }
175
+ @keyframes spin { to { transform: rotate(360deg); } }
176
+
177
+ /* ── CUSTOMIZE MODAL ── */
178
+ #customize-modal { display: none; position: fixed; inset: 0; background: rgba(0,0,0,0.7); z-index: 999; align-items: center; justify-content: center; backdrop-filter: blur(4px); }
179
+ #customize-modal.open { display: flex; }
180
+ .modal-box { background: #141414; border: 1px solid #2a2a2a; border-radius: 14px; padding: 24px; width: 400px; max-width: 90vw; }
181
+ .modal-title { font-size: 15px; font-weight: 600; color: #e5e5e5; margin-bottom: 16px; }
182
+ .modal-label { font-size: 12px; color: #777; margin-bottom: 4px; display: block; }
183
+ .modal-input { width: 100%; background: #1a1a1a; border: 1px solid #2a2a2a; border-radius: 8px; padding: 8px 12px; color: #e5e5e5; font-size: 13px; font-family: inherit; outline: none; margin-bottom: 12px; transition: border-color .15s; }
184
+ .modal-input:focus { border-color: #3a3a3a; }
185
+ .modal-textarea { min-height: 80px; resize: vertical; }
186
+ .modal-row { display: flex; gap: 8px; justify-content: flex-end; margin-top: 4px; }
187
+ .modal-btn { padding: 7px 16px; border-radius: 8px; font-size: 13px; cursor: pointer; font-family: inherit; border: 1px solid #2a2a2a; transition: all .15s; }
188
+ .modal-btn-cancel { background: transparent; color: #777; }
189
+ .modal-btn-cancel:hover { background: #1e1e1e; color: #ccc; }
190
+ .modal-btn-save { background: #c0392b; border-color: #c0392b; color: #fff; }
191
+ .modal-btn-save:hover { background: #e74c3c; border-color: #e74c3c; }
102
192
  </style>
103
193
  </head>
104
194
  <body>
105
195
 
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>
196
+ <!-- ── SIDEBAR ── -->
197
+ <aside id="sidebar">
198
+ <div class="sidebar-top">
199
+ <!-- Logo -->
200
+ <div class="sidebar-logo">
201
+ <span style="font-size:22px">🦅</span>
202
+ <span class="sidebar-logo-text">HyperClaw</span>
203
+ </div>
204
+
205
+ <!-- New chat -->
206
+ <button class="sidebar-new-chat" id="new-chat-btn">
207
+ <svg width="14" height="14" viewBox="0 0 14 14" fill="none"><path d="M7 1v12M1 7h12" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/></svg>
208
+ New chat
209
+ </button>
210
+
211
+ <!-- Search -->
212
+ <div class="sidebar-search" style="margin-top:6px">
213
+ <svg class="sidebar-search-icon" width="13" height="13" viewBox="0 0 13 13" fill="none"><circle cx="5.5" cy="5.5" r="4" stroke="currentColor" stroke-width="1.3"/><path d="M9 9l3 3" stroke="currentColor" stroke-width="1.3" stroke-linecap="round"/></svg>
214
+ <input type="text" id="search-input" placeholder="Search chats…" autocomplete="off">
215
+ </div>
216
+ </div>
217
+
218
+ <!-- Customize -->
219
+ <button class="sidebar-btn" id="customize-btn" style="margin: 2px 12px; width: calc(100% - 24px)">
220
+ <svg width="14" height="14" viewBox="0 0 14 14" fill="none"><circle cx="7" cy="7" r="2.5" stroke="currentColor" stroke-width="1.3"/><path d="M7 1v1.5M7 11.5V13M1 7h1.5M11.5 7H13M2.5 2.5l1.1 1.1M10.4 10.4l1.1 1.1M2.5 11.5l1.1-1.1M10.4 3.6l1.1-1.1" stroke="currentColor" stroke-width="1.3" stroke-linecap="round"/></svg>
221
+ Customize
222
+ </button>
223
+
224
+ <!-- Projects section -->
225
+ <div class="sidebar-section">Projects</div>
226
+ <div id="projects-list">
227
+ <button class="sidebar-project active" data-preset="" id="proj-general">
228
+ <span class="sidebar-project-dot" style="background:#6b7280"></span>
229
+ General
230
+ </button>
231
+ <button class="sidebar-project" data-preset="ethical-hacker" id="proj-hacker">
232
+ <span class="sidebar-project-dot" style="background:#ef4444"></span>
233
+ Ethical Hacker
234
+ </button>
235
+ <button class="sidebar-project" data-preset="hyperclaw" id="proj-hcdev">
236
+ <span class="sidebar-project-dot" style="background:#f97316"></span>
237
+ HyperClaw Dev
238
+ </button>
239
+ <button class="sidebar-project" data-preset="osint" id="proj-osint">
240
+ <span class="sidebar-project-dot" style="background:#a855f7"></span>
241
+ OSINT
242
+ </button>
243
+ <button class="sidebar-project" id="new-project-btn" style="color:#555">
244
+ <svg width="12" height="12" viewBox="0 0 12 12" fill="none"><path d="M6 1v10M1 6h10" stroke="currentColor" stroke-width="1.3" stroke-linecap="round"/></svg>
245
+ New project…
246
+ </button>
112
247
  </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>
248
+
249
+ <!-- Chats history -->
250
+ <div class="sidebar-section">Chats</div>
251
+ <div class="sidebar-history" id="chat-history">
252
+ <div style="padding: 6px 22px; font-size:11px; color:#3a3a3a">No recent chats</div>
117
253
  </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>
254
+
255
+ <!-- Bottom: status -->
256
+ <div class="sidebar-bottom">
257
+ <div style="display:flex;align-items:center;gap:6px">
258
+ <span class="dot" id="ws-dot-side"></span>
259
+ <span id="ws-label-side" style="font-size:11px;color:#444">Offline</span>
260
+ </div>
261
+ <div id="model-label" style="font-size:11px;color:#333;margin-top:2px;font-family:'JetBrains Mono',monospace;white-space:nowrap;overflow:hidden;text-overflow:ellipsis"></div>
121
262
  </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>
263
+ </aside>
264
+
265
+ <!-- ── MAIN ── -->
266
+ <div id="main">
267
+
268
+ <!-- Header -->
269
+ <header class="chat-header">
270
+ <div class="chat-header-left">
271
+ <button class="sidebar-toggle" id="sidebar-toggle" title="Toggle sidebar">
272
+ <svg width="16" height="16" viewBox="0 0 16 16" fill="none"><rect x="2" y="4" width="12" height="1.3" rx=".65" fill="currentColor"/><rect x="2" y="7.35" width="12" height="1.3" rx=".65" fill="currentColor"/><rect x="2" y="10.7" width="12" height="1.3" rx=".65" fill="currentColor"/></svg>
273
+ </button>
274
+ <span style="font-size:13px;color:#555;font-weight:500" id="active-project-label">General</span>
275
+ </div>
276
+ <div class="chat-header-right">
277
+ <div class="header-status">
278
+ <span class="dot" id="ws-dot"></span>
279
+ <span id="ws-label" style="font-size:12px;color:#444">Offline</span>
280
+ </div>
281
+ <button class="hdr-btn" id="clear-btn">Clear</button>
282
+ <button class="hdr-btn" id="terminal-btn">
283
+ <span style="display:inline-flex;align-items:center;gap:5px">
284
+ <svg width="12" height="12" viewBox="0 0 12 12" fill="none"><path d="M1.5 2.5L5 6L1.5 9.5M6.5 9.5H10.5" stroke="currentColor" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round"/></svg>
285
+ Terminal
286
+ </span>
287
+ </button>
288
+ </div>
289
+ </header>
290
+
291
+ <!-- Messages -->
292
+ <main id="messages">
293
+ <!-- Banner -->
294
+ <div class="hc-banner">
295
+ <div class="hc-banner-text">HYPERCLAW</div>
296
+ <div class="hc-banner-sub" id="banner-sub">HyperClaw Bot · AI Gateway v5.3.3</div>
297
+ </div>
298
+ <div class="welcome" id="welcome">
299
+ <div class="welcome-eagle">🦅</div>
300
+ <div class="welcome-title">What can I help with?</div>
301
+ <p class="welcome-sub">Your personal AI agent — running on your hardware. Pick a suggestion or type anything.</p>
302
+ <div class="welcome-chips">
303
+ <button class="chip" onclick="fillInput(this)">What can you do?</button>
304
+ <button class="chip" onclick="fillInput(this)">Search the web for AI news</button>
305
+ <button class="chip" onclick="fillInput(this)">Help me write a script</button>
306
+ <button class="chip" onclick="fillInput(this)">Show system status</button>
307
+ <button class="chip" onclick="fillInput(this)">Run a security audit</button>
308
+ </div>
309
+ </div>
310
+ </main>
311
+
312
+ <!-- Input -->
313
+ <div class="input-wrap">
314
+ <form class="input-row" id="form">
315
+ <textarea id="input" rows="1" placeholder="Ask HyperClaw anything… Enter to send, Shift+Enter for new line" autocomplete="off"></textarea>
316
+ <button type="submit" id="send-btn">
317
+ <svg width="16" height="16" viewBox="0 0 16 16" fill="none"><path d="M8 12V4M4 8l4-4 4 4" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"/></svg>
318
+ </button>
319
+ </form>
320
+ <div class="input-hint">🦅 HyperClaw · <span id="model-hint">—</span> · Shift+Enter for new line</div>
321
+ </div>
322
+
323
+ <!-- Terminal panel -->
324
+ <div id="terminal-panel">
325
+ <div class="term-titlebar">
326
+ <div class="term-dots">
327
+ <div class="term-dot" style="background:#ff5f57"></div>
328
+ <div class="term-dot" style="background:#febc2e"></div>
329
+ <div class="term-dot" style="background:#28c840"></div>
330
+ </div>
331
+ <span class="term-info" id="term-prompt-info">Terminal — HyperClaw gateway</span>
332
+ <div class="term-titlebar-right">
333
+ <button class="term-clear-btn" id="term-clear-btn">clear</button>
334
+ <button class="term-close-btn" id="term-close-btn" title="Close terminal">
335
+ <svg width="13" height="13" viewBox="0 0 13 13" fill="none"><path d="M1.5 1.5l10 10M11.5 1.5l-10 10" stroke="currentColor" stroke-width="1.4" stroke-linecap="round"/></svg>
336
+ </button>
337
+ </div>
338
+ </div>
339
+ <div class="term-quick">
340
+ <button class="term-quick-btn" data-cmd="npm install">npm install</button>
341
+ <button class="term-quick-btn" data-cmd="npm run build">npm run build</button>
342
+ <button class="term-quick-btn" data-cmd="npm test">npm test</button>
343
+ <button class="term-quick-btn" data-cmd="npx hyperclaw doctor --fix">doctor --fix</button>
344
+ <button class="term-quick-btn" data-cmd="npx hyperclaw gateway status">gateway status</button>
345
+ </div>
346
+ <div id="terminal-log"><span style="color:#333">Use the quick buttons above or type a command below.</span></div>
347
+ <div class="term-input-row">
348
+ <span class="term-prompt">›</span>
349
+ <input type="text" id="terminal-input" placeholder="Run a command…">
350
+ <div class="term-spinner" id="term-spinner"></div>
351
+ </div>
352
+ </div>
353
+
354
+ </div>
355
+
356
+ <!-- ── CUSTOMIZE MODAL ── -->
357
+ <div id="customize-modal">
358
+ <div class="modal-box">
359
+ <div class="modal-title">✦ New Project / Agent</div>
360
+ <label class="modal-label">Project name</label>
361
+ <input type="text" class="modal-input" id="modal-name" placeholder="e.g. Bug Hunter">
362
+ <label class="modal-label">System prompt</label>
363
+ <textarea class="modal-input modal-textarea" id="modal-prompt" placeholder="e.g. You are a security researcher. Focus on vulnerabilities and exploits."></textarea>
364
+ <label class="modal-label">Color</label>
365
+ <input type="color" class="modal-input" id="modal-color" value="#6b7280" style="height:36px;padding:2px 6px;cursor:pointer">
366
+ <div class="modal-row">
367
+ <button class="modal-btn modal-btn-cancel" id="modal-cancel">Cancel</button>
368
+ <button class="modal-btn modal-btn-save" id="modal-save">Create project</button>
135
369
  </div>
136
370
  </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="Your AI assistant awaits — type a message and press Enter to send · HyperClaw" autocomplete="off"></textarea>
143
- <button type="submit" id="send-btn">↑</button>
144
- </form>
145
- <div class="input-hint">🦅 HyperClaw · <span id="model-hint">—</span></div>
146
371
  </div>
147
372
 
148
373
  <script>
@@ -151,60 +376,244 @@
151
376
  const apiUrl = `http://${location.hostname}:${port}`;
152
377
 
153
378
  let ws = null, isStreaming = false, currentBubble = null;
379
+ let activePreset = '';
380
+ let customProjects = JSON.parse(localStorage.getItem('hc_projects') || '[]');
381
+ let chatHistory = JSON.parse(localStorage.getItem('hc_history') || '[]');
382
+ let currentChatId = Date.now().toString();
383
+ let currentChatMessages = [];
384
+
154
385
  const messagesEl = document.getElementById('messages');
155
386
  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
387
  const input = document.getElementById('input');
161
388
  const sendBtn = document.getElementById('send-btn');
162
389
  const form = document.getElementById('form');
163
390
 
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 ? '🌙' : '☀️';
391
+ const PROMPT_PREFIX = {
392
+ 'ethical-hacker': 'Act as an ethical hacker / security researcher. Authorized testing only. ',
393
+ 'hyperclaw': 'You are helping with HyperClaw development. Be concise and code-focused. ',
394
+ 'osint': 'Act as an OSINT analyst. Passive reconnaissance, open-source research. '
395
+ };
396
+
397
+ // ── Sidebar toggle ───────────────────────────────────
398
+ document.getElementById('sidebar-toggle').onclick = () => {
399
+ document.getElementById('sidebar').classList.toggle('collapsed');
171
400
  };
172
401
 
173
- // ── Clear ────────────────────────────────────────────
174
- document.getElementById('clear-btn').onclick = () => {
402
+ // ── Project switching ────────────────────────────────
403
+ function setActiveProject(preset, name, dotColor) {
404
+ activePreset = preset;
405
+ document.querySelectorAll('.sidebar-project').forEach(b => b.classList.remove('active'));
406
+ document.getElementById('active-project-label').textContent = name || 'General';
407
+ }
408
+
409
+ document.querySelectorAll('.sidebar-project[data-preset]').forEach(btn => {
410
+ btn.onclick = () => {
411
+ const names = { '': 'General', 'ethical-hacker': 'Ethical Hacker', 'hyperclaw': 'HyperClaw Dev', 'osint': 'OSINT' };
412
+ document.querySelectorAll('.sidebar-project').forEach(b => b.classList.remove('active'));
413
+ btn.classList.add('active');
414
+ setActiveProject(btn.dataset.preset, names[btn.dataset.preset]);
415
+ };
416
+ });
417
+
418
+ // ── Chat history ─────────────────────────────────────
419
+ function saveCurrentChat(firstMsg) {
420
+ if (!firstMsg) return;
421
+ const existing = chatHistory.find(c => c.id === currentChatId);
422
+ if (existing) { existing.title = firstMsg.slice(0, 40); }
423
+ else { chatHistory.unshift({ id: currentChatId, title: firstMsg.slice(0, 40), ts: Date.now() }); }
424
+ if (chatHistory.length > 30) chatHistory = chatHistory.slice(0, 30);
425
+ localStorage.setItem('hc_history', JSON.stringify(chatHistory));
426
+ renderHistory();
427
+ }
428
+
429
+ function renderHistory(filter) {
430
+ const el = document.getElementById('chat-history');
431
+ const items = filter
432
+ ? chatHistory.filter(c => c.title.toLowerCase().includes(filter.toLowerCase()))
433
+ : chatHistory;
434
+ if (!items.length) { el.innerHTML = '<div style="padding:6px 22px;font-size:11px;color:#3a3a3a">' + (filter ? 'No results' : 'No recent chats') + '</div>'; return; }
435
+ el.innerHTML = items.map(c => `<button class="sidebar-chat-item" onclick="loadChat('${c.id}')" title="${escHtml(c.title)}">💬 ${escHtml(c.title)}</button>`).join('');
436
+ }
437
+
438
+ function loadChat(id) {
439
+ // For now just label it — full persistence would require storing messages
440
+ const chat = chatHistory.find(c => c.id === id);
441
+ if (chat) document.getElementById('active-project-label').textContent = chat.title.slice(0,20) + '…';
442
+ }
443
+
444
+ renderHistory();
445
+
446
+ // Search chats
447
+ document.getElementById('search-input').oninput = function() { renderHistory(this.value); };
448
+
449
+ // ── New chat ─────────────────────────────────────────
450
+ document.getElementById('new-chat-btn').onclick = startNewChat;
451
+ document.getElementById('clear-btn').onclick = () => { clearMessages(); };
452
+
453
+ function startNewChat() {
454
+ currentChatId = Date.now().toString();
455
+ currentChatMessages = [];
456
+ clearMessages();
457
+ }
458
+
459
+ function clearMessages() {
175
460
  messagesEl.querySelectorAll('.msg-row').forEach(e => e.remove());
176
461
  welcomeEl.style.display = 'flex';
462
+ }
463
+
464
+ // ── Customize modal ──────────────────────────────────
465
+ document.getElementById('customize-btn').onclick = () => {
466
+ document.getElementById('customize-modal').classList.add('open');
467
+ document.getElementById('modal-name').focus();
177
468
  };
469
+ document.getElementById('new-project-btn').onclick = () => {
470
+ document.getElementById('customize-modal').classList.add('open');
471
+ document.getElementById('modal-name').focus();
472
+ };
473
+ document.getElementById('modal-cancel').onclick = () => document.getElementById('customize-modal').classList.remove('open');
474
+ document.getElementById('customize-modal').onclick = (e) => { if (e.target === e.currentTarget) e.currentTarget.classList.remove('open'); };
178
475
 
179
- // ── Chips ────────────────────────────────────────────
180
- function fillInput(el) {
181
- input.value = el.textContent;
182
- input.focus();
183
- autoResize();
476
+ document.getElementById('modal-save').onclick = () => {
477
+ const name = document.getElementById('modal-name').value.trim();
478
+ const prompt = document.getElementById('modal-prompt').value.trim();
479
+ const color = document.getElementById('modal-color').value;
480
+ if (!name) return;
481
+ const id = 'custom-' + Date.now();
482
+ customProjects.push({ id, name, prompt, color });
483
+ localStorage.setItem('hc_projects', JSON.stringify(customProjects));
484
+ PROMPT_PREFIX[id] = prompt + ' ';
485
+ addProjectToSidebar({ id, name, color });
486
+ document.getElementById('customize-modal').classList.remove('open');
487
+ document.getElementById('modal-name').value = '';
488
+ document.getElementById('modal-prompt').value = '';
489
+ document.getElementById('modal-color').value = '#6b7280';
490
+ };
491
+
492
+ function addProjectToSidebar(proj) {
493
+ const btn = document.createElement('button');
494
+ btn.className = 'sidebar-project';
495
+ btn.dataset.preset = proj.id;
496
+ btn.innerHTML = `<span class="sidebar-project-dot" style="background:${proj.color}"></span>${escHtml(proj.name)}`;
497
+ btn.onclick = () => {
498
+ document.querySelectorAll('.sidebar-project').forEach(b => b.classList.remove('active'));
499
+ btn.classList.add('active');
500
+ setActiveProject(proj.id, proj.name);
501
+ };
502
+ document.getElementById('new-project-btn').before(btn);
503
+ }
504
+
505
+ // Restore custom projects
506
+ customProjects.forEach(proj => {
507
+ PROMPT_PREFIX[proj.id] = proj.prompt + ' ';
508
+ addProjectToSidebar(proj);
509
+ });
510
+
511
+ // ── Terminal ─────────────────────────────────────────
512
+ const terminalPanel = document.getElementById('terminal-panel');
513
+ const terminalBtn = document.getElementById('terminal-btn');
514
+ const terminalLog = document.getElementById('terminal-log');
515
+ const terminalInput = document.getElementById('terminal-input');
516
+ const termSpinner = document.getElementById('term-spinner');
517
+ let termRunning = false;
518
+
519
+ terminalBtn.onclick = toggleTerminal;
520
+ document.getElementById('term-close-btn').onclick = () => {
521
+ terminalPanel.classList.remove('open');
522
+ terminalBtn.classList.remove('active');
523
+ };
524
+ document.getElementById('term-clear-btn').onclick = () => {
525
+ terminalLog.innerHTML = '<span style="color:#333">Terminal cleared.</span>';
526
+ };
527
+
528
+ function toggleTerminal() {
529
+ const isOpen = terminalPanel.classList.toggle('open');
530
+ terminalBtn.classList.toggle('active', isOpen);
531
+ if (isOpen) setTimeout(() => terminalInput.focus(), 50);
184
532
  }
185
533
 
534
+ async function runTerminalCmd(cmd) {
535
+ cmd = (cmd || '').trim();
536
+ if (!cmd || termRunning) return;
537
+ termRunning = true;
538
+ termSpinner.style.display = 'block';
539
+ document.querySelectorAll('.term-quick-btn').forEach(b => b.disabled = true);
540
+
541
+ const cmdLine = document.createElement('div');
542
+ cmdLine.className = 'term-log-cmd';
543
+ cmdLine.textContent = '$ ' + cmd;
544
+ if (terminalLog.querySelector('span')) terminalLog.innerHTML = '';
545
+ terminalLog.appendChild(cmdLine);
546
+ terminalLog.scrollTop = terminalLog.scrollHeight;
547
+
548
+ try {
549
+ const res = await fetch(apiUrl + '/api/terminal', {
550
+ method: 'POST', headers: {'Content-Type': 'application/json'},
551
+ body: JSON.stringify({command: cmd})
552
+ });
553
+ const data = await res.json();
554
+ if (data.user || data.cwd) {
555
+ document.getElementById('term-prompt-info').textContent = `${data.user || 'user'}@${data.hostname || 'local'}:${data.cwd || ''}`;
556
+ }
557
+ if (data.stdout) {
558
+ const out = document.createElement('div');
559
+ out.textContent = data.stdout.trimEnd();
560
+ terminalLog.appendChild(out);
561
+ }
562
+ if (data.stderr) {
563
+ const err = document.createElement('div');
564
+ err.className = 'term-log-err';
565
+ err.textContent = data.stderr.trimEnd();
566
+ terminalLog.appendChild(err);
567
+ }
568
+ if (!data.stdout && !data.stderr) {
569
+ const ex = document.createElement('div');
570
+ ex.style.color = '#444';
571
+ ex.textContent = '(exit ' + (data.code ?? 0) + ')';
572
+ terminalLog.appendChild(ex);
573
+ }
574
+ } catch (e) {
575
+ const err = document.createElement('div');
576
+ err.className = 'term-log-err';
577
+ err.textContent = 'Error: ' + (e.message || String(e));
578
+ terminalLog.appendChild(err);
579
+ } finally {
580
+ termRunning = false;
581
+ termSpinner.style.display = 'none';
582
+ document.querySelectorAll('.term-quick-btn').forEach(b => b.disabled = false);
583
+ terminalLog.scrollTop = terminalLog.scrollHeight;
584
+ terminalInput.focus();
585
+ }
586
+ }
587
+
588
+ document.querySelectorAll('.term-quick-btn').forEach(btn => {
589
+ btn.onclick = () => runTerminalCmd(btn.dataset.cmd);
590
+ });
591
+ terminalInput.onkeydown = (e) => {
592
+ if (e.key === 'Enter') { runTerminalCmd(terminalInput.value); terminalInput.value = ''; }
593
+ };
594
+
186
595
  // ── WebSocket ────────────────────────────────────────
187
596
  function setConnected(ok) {
188
- wsDot.className = 'dot' + (ok ? ' connected' : '');
189
- wsLabel.textContent = ok ? 'Connected' : 'Reconnecting…';
190
- wsLabel.style.color = ok ? '#22c55e' : 'var(--text2)';
597
+ [document.getElementById('ws-dot'), document.getElementById('ws-dot-side')].forEach(d => {
598
+ if (d) d.className = 'dot' + (ok ? ' connected' : '');
599
+ });
600
+ const label = ok ? 'Connected' : 'Reconnecting…';
601
+ const color = ok ? '#22c55e' : '#444';
602
+ [document.getElementById('ws-label'), document.getElementById('ws-label-side')].forEach(el => {
603
+ if (el) { el.textContent = label; el.style.color = color; }
604
+ });
191
605
  }
192
606
 
193
607
  function connectWs() {
194
608
  ws = new WebSocket(wsUrl);
195
- ws.onopen = () => { setConnected(true); };
609
+ ws.onopen = () => setConnected(true);
196
610
  ws.onclose = () => { setConnected(false); setTimeout(connectWs, 3000); };
197
611
  ws.onerror = () => setConnected(false);
198
612
  ws.onmessage = (e) => {
199
613
  try {
200
614
  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
- }
615
+ if (msg.type === 'chat:chunk') {
616
+ if (!currentBubble) { removeTyping(); currentBubble = addMsg('assistant', '', true); }
208
617
  currentBubble._raw = (currentBubble._raw || '') + (msg.content || '');
209
618
  currentBubble.textContent = currentBubble._raw;
210
619
  messagesEl.scrollTop = messagesEl.scrollHeight;
@@ -235,11 +644,8 @@
235
644
  messagesEl.scrollTop = messagesEl.scrollHeight;
236
645
  }
237
646
 
238
- // ── Message helpers ───────────────────────────────────
239
- function now() {
240
- return new Date().toLocaleTimeString([], {hour:'2-digit',minute:'2-digit'});
241
- }
242
- let lastTimestampEl = null;
647
+ // ── Messages ─────────────────────────────────────────
648
+ function now() { return new Date().toLocaleTimeString([], {hour:'2-digit',minute:'2-digit'}); }
243
649
 
244
650
  function addMsg(role, content, streaming = false) {
245
651
  welcomeEl.style.display = 'none';
@@ -263,11 +669,13 @@
263
669
  }
264
670
 
265
671
  function addTimestamp() {
266
- const t = document.createElement('div');
267
- t.className = 'msg-time';
268
- t.textContent = now();
269
672
  const last = messagesEl.querySelector('.msg-row.assistant:last-child');
270
- if (last) last.appendChild(t);
673
+ if (last) {
674
+ const t = document.createElement('div');
675
+ t.className = 'msg-time';
676
+ t.textContent = now();
677
+ last.appendChild(t);
678
+ }
271
679
  }
272
680
 
273
681
  let workingTicker = null;
@@ -277,12 +685,10 @@
277
685
  row.className = 'msg-row assistant';
278
686
  row.id = 'typing-row';
279
687
  let sec = 0;
280
- 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> <span id="working-text" style="margin-left:8px;color:var(--text2);font-size:12px">Working (0s)</span></div>';
688
+ row.innerHTML = '<div class="msg-avatar">🦅</div><div class="msg-bubble" style="padding:12px 16px"><span class="typing-dot"></span><span class="typing-dot"></span><span class="typing-dot"></span><span id="wt" style="margin-left:8px;color:#444;font-size:11px">Thinking…</span></div>';
281
689
  messagesEl.appendChild(row);
282
690
  messagesEl.scrollTop = messagesEl.scrollHeight;
283
- workingTicker = setInterval(() => {
284
- sec++; const el = document.getElementById('working-text'); if (el) el.textContent = 'Working (' + sec + 's)';
285
- }, 1000);
691
+ workingTicker = setInterval(() => { sec++; const el = document.getElementById('wt'); if (el) el.textContent = sec + 's'; }, 1000);
286
692
  }
287
693
 
288
694
  function removeTyping() {
@@ -290,13 +696,18 @@
290
696
  document.getElementById('typing-row')?.remove();
291
697
  }
292
698
 
293
- function escHtml(s) { return s.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;'); }
699
+ function escHtml(s) { return String(s).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;'); }
294
700
 
295
701
  // ── Send ─────────────────────────────────────────────
702
+ let msgCount = 0;
296
703
  form.onsubmit = async (e) => {
297
704
  e.preventDefault();
298
705
  const msg = input.value.trim();
299
706
  if (!msg || isStreaming) return;
707
+ const prefix = PROMPT_PREFIX[activePreset] || '';
708
+ const fullMsg = prefix ? prefix + msg : msg;
709
+ if (msgCount === 0) saveCurrentChat(msg);
710
+ msgCount++;
300
711
  addMsg('user', msg);
301
712
  input.value = '';
302
713
  input.style.height = 'auto';
@@ -305,13 +716,12 @@
305
716
  addTyping();
306
717
 
307
718
  if (ws && ws.readyState === WebSocket.OPEN) {
308
- ws.send(JSON.stringify({ type: 'chat:message', content: msg, source: 'webchat' }));
719
+ ws.send(JSON.stringify({ type: 'chat:message', content: fullMsg, source: 'webchat' }));
309
720
  } else {
310
- // REST fallback
311
721
  try {
312
722
  const res = await fetch(apiUrl + '/api/chat', {
313
723
  method: 'POST', headers: {'Content-Type':'application/json'},
314
- body: JSON.stringify({message: msg})
724
+ body: JSON.stringify({message: fullMsg})
315
725
  });
316
726
  const data = await res.json();
317
727
  removeTyping();
@@ -325,10 +735,13 @@
325
735
  }
326
736
  };
327
737
 
738
+ // ── Chips ────────────────────────────────────────────
739
+ function fillInput(el) { input.value = el.textContent; input.focus(); autoResize(); }
740
+
328
741
  // ── Textarea auto-resize ──────────────────────────────
329
742
  function autoResize() {
330
743
  input.style.height = 'auto';
331
- input.style.height = Math.min(input.scrollHeight, 120) + 'px';
744
+ input.style.height = Math.min(input.scrollHeight, 130) + 'px';
332
745
  }
333
746
  input.addEventListener('input', autoResize);
334
747
  input.addEventListener('keydown', (e) => {
@@ -338,8 +751,13 @@
338
751
  // ── Init ─────────────────────────────────────────────
339
752
  connectWs();
340
753
  fetch(apiUrl + '/api/status').then(r => r.json()).then(d => {
341
- if (d.agentName) agentSub.textContent = `${d.agentName} · ${d.model || '—'} · port ${d.port || port}`;
342
- if (d.model) modelHint.textContent = d.model;
754
+ if (d.model) {
755
+ document.getElementById('model-hint').textContent = d.model;
756
+ document.getElementById('model-label').textContent = d.model;
757
+ const ver = d.version || '5.3.3';
758
+ document.getElementById('banner-sub').textContent = `HyperClaw Bot \u00b7 AI Gateway v${ver}`;
759
+ }
760
+ document.documentElement.setAttribute('data-daemon', d.daemonMode ? 'true' : 'false');
343
761
  }).catch(() => {});
344
762
  </script>
345
763
  </body>