cf-claw 3.0.0

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 (273) hide show
  1. package/dist/agent.d.ts +15 -0
  2. package/dist/agent.d.ts.map +1 -0
  3. package/dist/agent.js +262 -0
  4. package/dist/agent.js.map +1 -0
  5. package/dist/agents.d.ts +51 -0
  6. package/dist/agents.d.ts.map +1 -0
  7. package/dist/agents.js +478 -0
  8. package/dist/agents.js.map +1 -0
  9. package/dist/api/routes.d.ts +3 -0
  10. package/dist/api/routes.d.ts.map +1 -0
  11. package/dist/api/routes.js +491 -0
  12. package/dist/api/routes.js.map +1 -0
  13. package/dist/bot.d.ts +4 -0
  14. package/dist/bot.d.ts.map +1 -0
  15. package/dist/bot.js +295 -0
  16. package/dist/bot.js.map +1 -0
  17. package/dist/canvas.d.ts +37 -0
  18. package/dist/canvas.d.ts.map +1 -0
  19. package/dist/canvas.js +47 -0
  20. package/dist/canvas.js.map +1 -0
  21. package/dist/cli.d.ts +3 -0
  22. package/dist/cli.d.ts.map +1 -0
  23. package/dist/cli.js +202 -0
  24. package/dist/cli.js.map +1 -0
  25. package/dist/commands.d.ts +6 -0
  26. package/dist/commands.d.ts.map +1 -0
  27. package/dist/commands.js +384 -0
  28. package/dist/commands.js.map +1 -0
  29. package/dist/components/TaskList.d.ts +7 -0
  30. package/dist/components/TaskList.d.ts.map +1 -0
  31. package/dist/components/TaskList.js +37 -0
  32. package/dist/components/TaskList.js.map +1 -0
  33. package/dist/config/encryption.d.ts +10 -0
  34. package/dist/config/encryption.d.ts.map +1 -0
  35. package/dist/config/encryption.js +111 -0
  36. package/dist/config/encryption.js.map +1 -0
  37. package/dist/config/json-config.d.ts +114 -0
  38. package/dist/config/json-config.d.ts.map +1 -0
  39. package/dist/config/json-config.js +388 -0
  40. package/dist/config/json-config.js.map +1 -0
  41. package/dist/config.d.ts +51 -0
  42. package/dist/config.d.ts.map +1 -0
  43. package/dist/config.js +137 -0
  44. package/dist/config.js.map +1 -0
  45. package/dist/context/pruning.d.ts +30 -0
  46. package/dist/context/pruning.d.ts.map +1 -0
  47. package/dist/context/pruning.js +132 -0
  48. package/dist/context/pruning.js.map +1 -0
  49. package/dist/dashboard/404/index.html +1 -0
  50. package/dist/dashboard/404.html +1 -0
  51. package/dist/dashboard/_next/static/chunks/117-c657912d4a6fa056.js +2 -0
  52. package/dist/dashboard/_next/static/chunks/191-a6922264096cb3ad.js +11 -0
  53. package/dist/dashboard/_next/static/chunks/343-71498a8257bc1e81.js +1 -0
  54. package/dist/dashboard/_next/static/chunks/app/_not-found/page-15cbe4395e02a084.js +1 -0
  55. package/dist/dashboard/_next/static/chunks/app/layout-191efbc962809bb4.js +1 -0
  56. package/dist/dashboard/_next/static/chunks/app/manual/page-3c401ecf89979cd7.js +1 -0
  57. package/dist/dashboard/_next/static/chunks/app/page-dff55e58941a3c4d.js +1 -0
  58. package/dist/dashboard/_next/static/chunks/fd9d1056-9583fa19bc194043.js +1 -0
  59. package/dist/dashboard/_next/static/chunks/framework-f66176bb897dc684.js +1 -0
  60. package/dist/dashboard/_next/static/chunks/main-2461f93106bcf687.js +1 -0
  61. package/dist/dashboard/_next/static/chunks/main-app-89f5ec28b3bb0e7f.js +1 -0
  62. package/dist/dashboard/_next/static/chunks/pages/_app-72b849fbd24ac258.js +1 -0
  63. package/dist/dashboard/_next/static/chunks/pages/_error-7ba65e1336b92748.js +1 -0
  64. package/dist/dashboard/_next/static/chunks/polyfills-42372ed130431b0a.js +1 -0
  65. package/dist/dashboard/_next/static/chunks/webpack-616e068a201ad621.js +1 -0
  66. package/dist/dashboard/_next/static/css/baff0f221c10680b.css +3 -0
  67. package/dist/dashboard/_next/static/pyqPyo6dkz4uTWdfdFAOJ/_buildManifest.js +1 -0
  68. package/dist/dashboard/_next/static/pyqPyo6dkz4uTWdfdFAOJ/_ssgManifest.js +1 -0
  69. package/dist/dashboard/index.html +1 -0
  70. package/dist/dashboard/index.txt +7 -0
  71. package/dist/dashboard/manual/index.html +1 -0
  72. package/dist/dashboard/manual/index.txt +7 -0
  73. package/dist/documents.d.ts +20 -0
  74. package/dist/documents.d.ts.map +1 -0
  75. package/dist/documents.js +227 -0
  76. package/dist/documents.js.map +1 -0
  77. package/dist/embeddings.d.ts +15 -0
  78. package/dist/embeddings.d.ts.map +1 -0
  79. package/dist/embeddings.js +40 -0
  80. package/dist/embeddings.js.map +1 -0
  81. package/dist/factory.d.ts +72 -0
  82. package/dist/factory.d.ts.map +1 -0
  83. package/dist/factory.js +2010 -0
  84. package/dist/factory.js.map +1 -0
  85. package/dist/groups.d.ts +13 -0
  86. package/dist/groups.d.ts.map +1 -0
  87. package/dist/groups.js +42 -0
  88. package/dist/groups.js.map +1 -0
  89. package/dist/index.d.ts +2 -0
  90. package/dist/index.d.ts.map +1 -0
  91. package/dist/index.js +223 -0
  92. package/dist/index.js.map +1 -0
  93. package/dist/lib/taskStorage.d.ts +4 -0
  94. package/dist/lib/taskStorage.d.ts.map +1 -0
  95. package/dist/lib/taskStorage.js +28 -0
  96. package/dist/lib/taskStorage.js.map +1 -0
  97. package/dist/llm/anthropic.d.ts +13 -0
  98. package/dist/llm/anthropic.d.ts.map +1 -0
  99. package/dist/llm/anthropic.js +96 -0
  100. package/dist/llm/anthropic.js.map +1 -0
  101. package/dist/llm/failover.d.ts +13 -0
  102. package/dist/llm/failover.d.ts.map +1 -0
  103. package/dist/llm/failover.js +42 -0
  104. package/dist/llm/failover.js.map +1 -0
  105. package/dist/llm/google.d.ts +13 -0
  106. package/dist/llm/google.d.ts.map +1 -0
  107. package/dist/llm/google.js +112 -0
  108. package/dist/llm/google.js.map +1 -0
  109. package/dist/llm/groq.d.ts +8 -0
  110. package/dist/llm/groq.d.ts.map +1 -0
  111. package/dist/llm/groq.js +13 -0
  112. package/dist/llm/groq.js.map +1 -0
  113. package/dist/llm/index.d.ts +11 -0
  114. package/dist/llm/index.d.ts.map +1 -0
  115. package/dist/llm/index.js +10 -0
  116. package/dist/llm/index.js.map +1 -0
  117. package/dist/llm/ollama.d.ts +9 -0
  118. package/dist/llm/ollama.d.ts.map +1 -0
  119. package/dist/llm/ollama.js +27 -0
  120. package/dist/llm/ollama.js.map +1 -0
  121. package/dist/llm/openai-compat.d.ts +17 -0
  122. package/dist/llm/openai-compat.d.ts.map +1 -0
  123. package/dist/llm/openai-compat.js +69 -0
  124. package/dist/llm/openai-compat.js.map +1 -0
  125. package/dist/llm/openrouter.d.ts +8 -0
  126. package/dist/llm/openrouter.d.ts.map +1 -0
  127. package/dist/llm/openrouter.js +20 -0
  128. package/dist/llm/openrouter.js.map +1 -0
  129. package/dist/llm/provider.d.ts +41 -0
  130. package/dist/llm/provider.d.ts.map +1 -0
  131. package/dist/llm/provider.js +2 -0
  132. package/dist/llm/provider.js.map +1 -0
  133. package/dist/llm/registry.d.ts +10 -0
  134. package/dist/llm/registry.d.ts.map +1 -0
  135. package/dist/llm/registry.js +90 -0
  136. package/dist/llm/registry.js.map +1 -0
  137. package/dist/llm/thinking.d.ts +7 -0
  138. package/dist/llm/thinking.d.ts.map +1 -0
  139. package/dist/llm/thinking.js +34 -0
  140. package/dist/llm/thinking.js.map +1 -0
  141. package/dist/llm.d.ts +17 -0
  142. package/dist/llm.d.ts.map +1 -0
  143. package/dist/llm.js +184 -0
  144. package/dist/llm.js.map +1 -0
  145. package/dist/logs.d.ts +11 -0
  146. package/dist/logs.d.ts.map +1 -0
  147. package/dist/logs.js +54 -0
  148. package/dist/logs.js.map +1 -0
  149. package/dist/memory/knowledge-graph.d.ts +34 -0
  150. package/dist/memory/knowledge-graph.d.ts.map +1 -0
  151. package/dist/memory/knowledge-graph.js +137 -0
  152. package/dist/memory/knowledge-graph.js.map +1 -0
  153. package/dist/memory.d.ts +73 -0
  154. package/dist/memory.d.ts.map +1 -0
  155. package/dist/memory.js +320 -0
  156. package/dist/memory.js.map +1 -0
  157. package/dist/mesh.d.ts +18 -0
  158. package/dist/mesh.d.ts.map +1 -0
  159. package/dist/mesh.js +120 -0
  160. package/dist/mesh.js.map +1 -0
  161. package/dist/paths.d.ts +11 -0
  162. package/dist/paths.d.ts.map +1 -0
  163. package/dist/paths.js +51 -0
  164. package/dist/paths.js.map +1 -0
  165. package/dist/proactive/heartbeat.d.ts +4 -0
  166. package/dist/proactive/heartbeat.d.ts.map +1 -0
  167. package/dist/proactive/heartbeat.js +56 -0
  168. package/dist/proactive/heartbeat.js.map +1 -0
  169. package/dist/proactive/recap.d.ts +12 -0
  170. package/dist/proactive/recap.d.ts.map +1 -0
  171. package/dist/proactive/recap.js +90 -0
  172. package/dist/proactive/recap.js.map +1 -0
  173. package/dist/proactive/recommendations.d.ts +11 -0
  174. package/dist/proactive/recommendations.d.ts.map +1 -0
  175. package/dist/proactive/recommendations.js +92 -0
  176. package/dist/proactive/recommendations.js.map +1 -0
  177. package/dist/projects.d.ts +44 -0
  178. package/dist/projects.d.ts.map +1 -0
  179. package/dist/projects.js +101 -0
  180. package/dist/projects.js.map +1 -0
  181. package/dist/scheduler/index.d.ts +17 -0
  182. package/dist/scheduler/index.d.ts.map +1 -0
  183. package/dist/scheduler/index.js +116 -0
  184. package/dist/scheduler/index.js.map +1 -0
  185. package/dist/sessions.d.ts +19 -0
  186. package/dist/sessions.d.ts.map +1 -0
  187. package/dist/sessions.js +176 -0
  188. package/dist/sessions.js.map +1 -0
  189. package/dist/skills/index.d.ts +14 -0
  190. package/dist/skills/index.d.ts.map +1 -0
  191. package/dist/skills/index.js +126 -0
  192. package/dist/skills/index.js.map +1 -0
  193. package/dist/swarm.d.ts +18 -0
  194. package/dist/swarm.d.ts.map +1 -0
  195. package/dist/swarm.js +146 -0
  196. package/dist/swarm.js.map +1 -0
  197. package/dist/taskListWidget.d.ts +76 -0
  198. package/dist/taskListWidget.d.ts.map +1 -0
  199. package/dist/taskListWidget.js +312 -0
  200. package/dist/taskListWidget.js.map +1 -0
  201. package/dist/tools/browser.d.ts +5 -0
  202. package/dist/tools/browser.d.ts.map +1 -0
  203. package/dist/tools/browser.js +104 -0
  204. package/dist/tools/browser.js.map +1 -0
  205. package/dist/tools/config-tools.d.ts +4 -0
  206. package/dist/tools/config-tools.d.ts.map +1 -0
  207. package/dist/tools/config-tools.js +154 -0
  208. package/dist/tools/config-tools.js.map +1 -0
  209. package/dist/tools/files.d.ts +3 -0
  210. package/dist/tools/files.d.ts.map +1 -0
  211. package/dist/tools/files.js +208 -0
  212. package/dist/tools/files.js.map +1 -0
  213. package/dist/tools/index.d.ts +11 -0
  214. package/dist/tools/index.d.ts.map +1 -0
  215. package/dist/tools/index.js +1109 -0
  216. package/dist/tools/index.js.map +1 -0
  217. package/dist/tools/mcp-bridge.d.ts +5 -0
  218. package/dist/tools/mcp-bridge.d.ts.map +1 -0
  219. package/dist/tools/mcp-bridge.js +200 -0
  220. package/dist/tools/mcp-bridge.js.map +1 -0
  221. package/dist/tools/shell.d.ts +8 -0
  222. package/dist/tools/shell.d.ts.map +1 -0
  223. package/dist/tools/shell.js +78 -0
  224. package/dist/tools/shell.js.map +1 -0
  225. package/dist/tools/skills-manage.d.ts +25 -0
  226. package/dist/tools/skills-manage.d.ts.map +1 -0
  227. package/dist/tools/skills-manage.js +155 -0
  228. package/dist/tools/skills-manage.js.map +1 -0
  229. package/dist/tools/web-search.d.ts +4 -0
  230. package/dist/tools/web-search.d.ts.map +1 -0
  231. package/dist/tools/web-search.js +60 -0
  232. package/dist/tools/web-search.js.map +1 -0
  233. package/dist/tts.d.ts +6 -0
  234. package/dist/tts.d.ts.map +1 -0
  235. package/dist/tts.js +42 -0
  236. package/dist/tts.js.map +1 -0
  237. package/dist/types/task.d.ts +7 -0
  238. package/dist/types/task.d.ts.map +1 -0
  239. package/dist/types/task.js +2 -0
  240. package/dist/types/task.js.map +1 -0
  241. package/dist/typing.d.ts +18 -0
  242. package/dist/typing.d.ts.map +1 -0
  243. package/dist/typing.js +63 -0
  244. package/dist/typing.js.map +1 -0
  245. package/dist/usage.d.ts +28 -0
  246. package/dist/usage.d.ts.map +1 -0
  247. package/dist/usage.js +69 -0
  248. package/dist/usage.js.map +1 -0
  249. package/dist/voice.d.ts +6 -0
  250. package/dist/voice.d.ts.map +1 -0
  251. package/dist/voice.js +47 -0
  252. package/dist/voice.js.map +1 -0
  253. package/dist/webchat/index.d.ts +3 -0
  254. package/dist/webchat/index.d.ts.map +1 -0
  255. package/dist/webchat/index.js +3 -0
  256. package/dist/webchat/index.js.map +1 -0
  257. package/dist/webchat/public/index.html +344 -0
  258. package/dist/webchat/public/public/index.html +344 -0
  259. package/dist/webchat/public/public/task-list-widget.js +410 -0
  260. package/dist/webchat/public/task-list-widget.js +410 -0
  261. package/dist/webchat/server.d.ts +3 -0
  262. package/dist/webchat/server.d.ts.map +1 -0
  263. package/dist/webchat/server.js +80 -0
  264. package/dist/webchat/server.js.map +1 -0
  265. package/dist/webchat/ws.d.ts +4 -0
  266. package/dist/webchat/ws.d.ts.map +1 -0
  267. package/dist/webchat/ws.js +232 -0
  268. package/dist/webchat/ws.js.map +1 -0
  269. package/dist/webhooks/index.d.ts +14 -0
  270. package/dist/webhooks/index.d.ts.map +1 -0
  271. package/dist/webhooks/index.js +86 -0
  272. package/dist/webhooks/index.js.map +1 -0
  273. package/package.json +53 -0
@@ -0,0 +1,344 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>CF Claw - WebChat</title>
7
+ <script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
8
+ <style>
9
+ *,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
10
+ :root{
11
+ --bg:#0d1117;--surface:#161b22;--surface-alt:#1c2129;
12
+ --border:#30363d;--text:#e6edf3;--text-muted:#8b949e;
13
+ --accent:#58a6ff;--accent-dim:#1f6feb;
14
+ --user-bg:#1f6feb;--assistant-bg:#21262d;
15
+ --radius:12px;--max-w:800px;
16
+ }
17
+ html,body{height:100%;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Helvetica,Arial,sans-serif;background:var(--bg);color:var(--text)}
18
+
19
+ #app{display:flex;flex-direction:column;height:100vh}
20
+
21
+ header{display:flex;align-items:center;justify-content:space-between;padding:12px 20px;background:var(--surface);border-bottom:1px solid var(--border);flex-shrink:0}
22
+ header h1{font-size:16px;font-weight:600;color:var(--text);display:flex;align-items:center;gap:8px}
23
+ header h1 span{font-size:20px}
24
+ #status{font-size:12px;color:var(--text-muted);display:flex;align-items:center;gap:6px}
25
+ #status .dot{width:8px;height:8px;border-radius:50%;background:#f85149}
26
+ #status.connected .dot{background:#3fb950}
27
+
28
+ #auth-overlay{position:fixed;inset:0;z-index:100;background:var(--bg);display:flex;align-items:center;justify-content:center}
29
+ #auth-overlay.hidden{display:none}
30
+ .auth-card{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:32px;width:360px;max-width:90vw}
31
+ .auth-card h2{font-size:18px;margin-bottom:8px}
32
+ .auth-card p{font-size:13px;color:var(--text-muted);margin-bottom:20px}
33
+ .auth-card input{width:100%;padding:10px 12px;background:var(--bg);border:1px solid var(--border);border-radius:8px;color:var(--text);font-size:14px;outline:none}
34
+ .auth-card input:focus{border-color:var(--accent)}
35
+ .auth-card button{width:100%;margin-top:12px;padding:10px;background:var(--accent-dim);color:#fff;border:none;border-radius:8px;font-size:14px;font-weight:600;cursor:pointer;transition:background .2s}
36
+ .auth-card button:hover{background:var(--accent)}
37
+ .auth-error{color:#f85149;font-size:12px;margin-top:8px;display:none}
38
+
39
+ #messages{flex:1;overflow-y:auto;padding:16px 20px;display:flex;flex-direction:column;gap:12px}
40
+ .msg{max-width:85%;padding:12px 16px;border-radius:var(--radius);line-height:1.5;font-size:14px;word-wrap:break-word;overflow-wrap:break-word}
41
+ .msg.user{align-self:flex-end;background:var(--user-bg);color:#fff;border-bottom-right-radius:4px}
42
+ .msg.assistant{align-self:flex-start;background:var(--assistant-bg);border:1px solid var(--border);border-bottom-left-radius:4px}
43
+ .msg.assistant p{margin-bottom:8px}
44
+ .msg.assistant p:last-child{margin-bottom:0}
45
+ .msg.assistant code{background:var(--bg);padding:2px 6px;border-radius:4px;font-size:13px;font-family:'SF Mono',Monaco,Consolas,monospace}
46
+ .msg.assistant pre{background:var(--bg);border:1px solid var(--border);border-radius:8px;padding:12px;overflow-x:auto;margin:8px 0}
47
+ .msg.assistant pre code{background:none;padding:0}
48
+ .msg.assistant ul,.msg.assistant ol{padding-left:20px;margin:8px 0}
49
+ .msg.assistant blockquote{border-left:3px solid var(--accent);padding-left:12px;color:var(--text-muted);margin:8px 0}
50
+ .msg.assistant h1,.msg.assistant h2,.msg.assistant h3,.msg.assistant h4{margin:12px 0 6px}
51
+ .msg.assistant a{color:var(--accent)}
52
+ .msg.assistant table{border-collapse:collapse;margin:8px 0;width:100%}
53
+ .msg.assistant th,.msg.assistant td{border:1px solid var(--border);padding:6px 10px;text-align:left;font-size:13px}
54
+ .msg.assistant th{background:var(--surface)}
55
+ .msg.file-msg{align-self:center;background:var(--surface-alt);border:1px dashed var(--border);color:var(--text-muted);font-size:13px;padding:8px 14px;border-radius:8px}
56
+
57
+ .typing-indicator{align-self:flex-start;padding:12px 20px;background:var(--assistant-bg);border:1px solid var(--border);border-radius:var(--radius);border-bottom-left-radius:4px;display:none;gap:4px;align-items:center}
58
+ .typing-indicator.visible{display:flex}
59
+ .typing-indicator span{width:8px;height:8px;background:var(--text-muted);border-radius:50%;animation:bounce 1.4s infinite both}
60
+ .typing-indicator span:nth-child(2){animation-delay:.2s}
61
+ .typing-indicator span:nth-child(3){animation-delay:.4s}
62
+ @keyframes bounce{0%,80%,100%{transform:scale(.6);opacity:.4}40%{transform:scale(1);opacity:1}}
63
+
64
+ #input-area{padding:12px 20px 20px;background:var(--surface);border-top:1px solid var(--border);flex-shrink:0}
65
+ #input-row{display:flex;gap:8px;align-items:flex-end;max-width:var(--max-w);margin:0 auto}
66
+ #msg-input{flex:1;padding:10px 14px;background:var(--bg);border:1px solid var(--border);border-radius:var(--radius);color:var(--text);font-size:14px;resize:none;max-height:120px;outline:none;font-family:inherit;line-height:1.4}
67
+ #msg-input:focus{border-color:var(--accent)}
68
+ #msg-input::placeholder{color:var(--text-muted)}
69
+ .icon-btn{width:40px;height:40px;border-radius:10px;background:var(--bg);border:1px solid var(--border);color:var(--text-muted);cursor:pointer;display:flex;align-items:center;justify-content:center;transition:all .2s;flex-shrink:0}
70
+ .icon-btn:hover{border-color:var(--accent);color:var(--accent)}
71
+ #send-btn{background:var(--accent-dim);border-color:var(--accent-dim);color:#fff}
72
+ #send-btn:hover{background:var(--accent)}
73
+ #send-btn:disabled{opacity:.4;cursor:not-allowed}
74
+
75
+ #drop-zone{position:fixed;inset:0;z-index:90;background:rgba(13,17,23,.85);display:none;align-items:center;justify-content:center}
76
+ #drop-zone.visible{display:flex}
77
+ .drop-inner{border:2px dashed var(--accent);border-radius:16px;padding:48px;text-align:center;color:var(--accent)}
78
+ .drop-inner span{font-size:48px;display:block;margin-bottom:12px}
79
+ .drop-inner p{font-size:16px}
80
+
81
+ #file-input{display:none}
82
+
83
+ @media(max-width:600px){
84
+ header{padding:10px 14px}
85
+ #messages{padding:12px 10px}
86
+ #input-area{padding:10px 10px 14px}
87
+ .msg{max-width:92%}
88
+ }
89
+ </style>
90
+ </head>
91
+ <body>
92
+
93
+ <div id="auth-overlay">
94
+ <div class="auth-card">
95
+ <h2><span>🦀</span> CF Claw</h2>
96
+ <p>Enter your access token to continue.</p>
97
+ <input type="password" id="auth-input" placeholder="Access token" autocomplete="off">
98
+ <div class="auth-error" id="auth-error">Invalid token. Try again.</div>
99
+ <button id="auth-btn">Connect</button>
100
+ </div>
101
+ </div>
102
+
103
+ <div id="app">
104
+ <header>
105
+ <h1><span>🦀</span> CF Claw</h1>
106
+ <div id="status"><span class="dot"></span> <span id="status-text">Disconnected</span></div>
107
+ </header>
108
+
109
+ <main id="messages"></main>
110
+
111
+ <div id="drop-zone">
112
+ <div class="drop-inner">
113
+ <span>📄</span>
114
+ <p>Drop file here to upload</p>
115
+ </div>
116
+ </div>
117
+
118
+ <div id="input-area">
119
+ <div id="input-row">
120
+ <button class="icon-btn" id="attach-btn" title="Attach file">
121
+ <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21.44 11.05l-9.19 9.19a6 6 0 01-8.49-8.49l9.19-9.19a4 4 0 015.66 5.66l-9.2 9.19a2 2 0 01-2.83-2.83l8.49-8.48"/></svg>
122
+ </button>
123
+ <textarea id="msg-input" rows="1" placeholder="Type a message..."></textarea>
124
+ <button class="icon-btn" id="send-btn" title="Send" disabled>
125
+ <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="22" y1="2" x2="11" y2="13"/><polygon points="22 2 15 22 11 13 2 9 22 2"/></svg>
126
+ </button>
127
+ </div>
128
+ <input type="file" id="file-input">
129
+ </div>
130
+ </div>
131
+
132
+ <script>
133
+ (function(){
134
+ const $ = s => document.querySelector(s);
135
+ const messages = $('#messages');
136
+ const msgInput = $('#msg-input');
137
+ const sendBtn = $('#send-btn');
138
+ const attachBtn = $('#attach-btn');
139
+ const fileInput = $('#file-input');
140
+ const dropZone = $('#drop-zone');
141
+ const authOverlay = $('#auth-overlay');
142
+ const authInput = $('#auth-input');
143
+ const authBtn = $('#auth-btn');
144
+ const authError = $('#auth-error');
145
+ const statusEl = $('#status');
146
+ const statusText = $('#status-text');
147
+
148
+ let ws = null;
149
+ let ready = false;
150
+
151
+ marked.setOptions({ breaks: true, gfm: true });
152
+
153
+ function getToken() {
154
+ return sessionStorage.getItem('cf-claw-token') || '';
155
+ }
156
+
157
+ function setToken(t) {
158
+ sessionStorage.setItem('cf-claw-token', t);
159
+ }
160
+
161
+ function connect() {
162
+ const proto = location.protocol === 'https:' ? 'wss' : 'ws';
163
+ ws = new WebSocket(`${proto}://${location.host}/ws`);
164
+
165
+ ws.onopen = () => {
166
+ const token = getToken();
167
+ if (token || !requiresAuth()) {
168
+ ws.send(JSON.stringify({ type: 'auth', token }));
169
+ }
170
+ };
171
+
172
+ ws.onmessage = (e) => {
173
+ const msg = JSON.parse(e.data);
174
+ handleServerMessage(msg);
175
+ };
176
+
177
+ ws.onclose = () => {
178
+ setStatus(false);
179
+ ready = false;
180
+ setTimeout(connect, 3000);
181
+ };
182
+
183
+ ws.onerror = () => {
184
+ setStatus(false);
185
+ };
186
+ }
187
+
188
+ function requiresAuth() {
189
+ return true;
190
+ }
191
+
192
+ function handleServerMessage(msg) {
193
+ switch(msg.type) {
194
+ case 'auth_ok':
195
+ authOverlay.classList.add('hidden');
196
+ setStatus(true);
197
+ ready = true;
198
+ break;
199
+ case 'auth_error':
200
+ authOverlay.classList.remove('hidden');
201
+ authError.style.display = 'block';
202
+ authError.textContent = msg.reason || 'Authentication failed';
203
+ ready = false;
204
+ if (ws) ws.close();
205
+ break;
206
+ case 'typing':
207
+ showTyping();
208
+ break;
209
+ case 'response':
210
+ hideTyping();
211
+ addMessage('assistant', msg.text);
212
+ break;
213
+ case 'error':
214
+ hideTyping();
215
+ addMessage('assistant', `⚠️ ${msg.message}`);
216
+ break;
217
+ }
218
+ }
219
+
220
+ function setStatus(connected) {
221
+ statusEl.className = connected ? 'connected' : '';
222
+ statusText.textContent = connected ? 'Connected' : 'Disconnected';
223
+ sendBtn.disabled = !connected;
224
+ }
225
+
226
+ function showTyping() {
227
+ let el = messages.querySelector('.typing-indicator');
228
+ if (!el) {
229
+ el = document.createElement('div');
230
+ el.className = 'typing-indicator';
231
+ el.innerHTML = '<span></span><span></span><span></span>';
232
+ messages.appendChild(el);
233
+ }
234
+ el.classList.add('visible');
235
+ scrollToBottom();
236
+ }
237
+
238
+ function hideTyping() {
239
+ const el = messages.querySelector('.typing-indicator');
240
+ if (el) el.classList.remove('visible');
241
+ }
242
+
243
+ function addMessage(role, text) {
244
+ hideTyping();
245
+ const div = document.createElement('div');
246
+ div.className = `msg ${role}`;
247
+ if (role === 'assistant') {
248
+ div.innerHTML = marked.parse(text || '');
249
+ } else {
250
+ div.textContent = text;
251
+ }
252
+ messages.appendChild(div);
253
+ scrollToBottom();
254
+ }
255
+
256
+ function addFileMessage(name) {
257
+ const div = document.createElement('div');
258
+ div.className = 'msg file-msg';
259
+ div.textContent = `📎 ${name}`;
260
+ messages.appendChild(div);
261
+ scrollToBottom();
262
+ }
263
+
264
+ function scrollToBottom() {
265
+ requestAnimationFrame(() => {
266
+ messages.scrollTop = messages.scrollHeight;
267
+ });
268
+ }
269
+
270
+ function sendText(text) {
271
+ if (!ws || !ready || !text.trim()) return;
272
+ ws.send(JSON.stringify({ type: 'message', text }));
273
+ addMessage('user', text);
274
+ msgInput.value = '';
275
+ autoResize();
276
+ }
277
+
278
+ function sendFile(file) {
279
+ if (!ws || !ready) return;
280
+ const reader = new FileReader();
281
+ reader.onload = () => {
282
+ const base64 = reader.result.split(',')[1];
283
+ ws.send(JSON.stringify({
284
+ type: 'file',
285
+ name: file.name,
286
+ mime: file.type,
287
+ data: base64,
288
+ text: ''
289
+ }));
290
+ addFileMessage(file.name);
291
+ };
292
+ reader.readAsDataURL(file);
293
+ }
294
+
295
+ function autoResize() {
296
+ msgInput.style.height = 'auto';
297
+ msgInput.style.height = Math.min(msgInput.scrollHeight, 120) + 'px';
298
+ }
299
+
300
+ sendBtn.addEventListener('click', () => sendText(msgInput.value));
301
+ msgInput.addEventListener('keydown', (e) => {
302
+ if (e.key === 'Enter' && !e.shiftKey) {
303
+ e.preventDefault();
304
+ sendText(msgInput.value);
305
+ }
306
+ });
307
+ msgInput.addEventListener('input', autoResize);
308
+
309
+ attachBtn.addEventListener('click', () => fileInput.click());
310
+ fileInput.addEventListener('change', () => {
311
+ if (fileInput.files.length > 0) {
312
+ sendFile(fileInput.files[0]);
313
+ fileInput.value = '';
314
+ }
315
+ });
316
+
317
+ document.addEventListener('dragover', (e) => { e.preventDefault(); dropZone.classList.add('visible'); });
318
+ document.addEventListener('dragleave', (e) => { if (e.relatedTarget === null) dropZone.classList.remove('visible'); });
319
+ dropZone.addEventListener('dragleave', () => dropZone.classList.remove('visible'));
320
+ dropZone.addEventListener('drop', (e) => {
321
+ e.preventDefault();
322
+ dropZone.classList.remove('visible');
323
+ if (e.dataTransfer.files.length > 0) sendFile(e.dataTransfer.files[0]);
324
+ });
325
+
326
+ authBtn.addEventListener('click', () => {
327
+ const token = authInput.value.trim();
328
+ setToken(token);
329
+ authError.style.display = 'none';
330
+ connect();
331
+ });
332
+ authInput.addEventListener('keydown', (e) => {
333
+ if (e.key === 'Enter') authBtn.click();
334
+ });
335
+
336
+ const saved = getToken();
337
+ if (saved) {
338
+ authOverlay.classList.add('hidden');
339
+ connect();
340
+ }
341
+ })();
342
+ </script>
343
+ </body>
344
+ </html>
@@ -0,0 +1,344 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>CF Claw - WebChat</title>
7
+ <script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
8
+ <style>
9
+ *,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
10
+ :root{
11
+ --bg:#0d1117;--surface:#161b22;--surface-alt:#1c2129;
12
+ --border:#30363d;--text:#e6edf3;--text-muted:#8b949e;
13
+ --accent:#58a6ff;--accent-dim:#1f6feb;
14
+ --user-bg:#1f6feb;--assistant-bg:#21262d;
15
+ --radius:12px;--max-w:800px;
16
+ }
17
+ html,body{height:100%;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Helvetica,Arial,sans-serif;background:var(--bg);color:var(--text)}
18
+
19
+ #app{display:flex;flex-direction:column;height:100vh}
20
+
21
+ header{display:flex;align-items:center;justify-content:space-between;padding:12px 20px;background:var(--surface);border-bottom:1px solid var(--border);flex-shrink:0}
22
+ header h1{font-size:16px;font-weight:600;color:var(--text);display:flex;align-items:center;gap:8px}
23
+ header h1 span{font-size:20px}
24
+ #status{font-size:12px;color:var(--text-muted);display:flex;align-items:center;gap:6px}
25
+ #status .dot{width:8px;height:8px;border-radius:50%;background:#f85149}
26
+ #status.connected .dot{background:#3fb950}
27
+
28
+ #auth-overlay{position:fixed;inset:0;z-index:100;background:var(--bg);display:flex;align-items:center;justify-content:center}
29
+ #auth-overlay.hidden{display:none}
30
+ .auth-card{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:32px;width:360px;max-width:90vw}
31
+ .auth-card h2{font-size:18px;margin-bottom:8px}
32
+ .auth-card p{font-size:13px;color:var(--text-muted);margin-bottom:20px}
33
+ .auth-card input{width:100%;padding:10px 12px;background:var(--bg);border:1px solid var(--border);border-radius:8px;color:var(--text);font-size:14px;outline:none}
34
+ .auth-card input:focus{border-color:var(--accent)}
35
+ .auth-card button{width:100%;margin-top:12px;padding:10px;background:var(--accent-dim);color:#fff;border:none;border-radius:8px;font-size:14px;font-weight:600;cursor:pointer;transition:background .2s}
36
+ .auth-card button:hover{background:var(--accent)}
37
+ .auth-error{color:#f85149;font-size:12px;margin-top:8px;display:none}
38
+
39
+ #messages{flex:1;overflow-y:auto;padding:16px 20px;display:flex;flex-direction:column;gap:12px}
40
+ .msg{max-width:85%;padding:12px 16px;border-radius:var(--radius);line-height:1.5;font-size:14px;word-wrap:break-word;overflow-wrap:break-word}
41
+ .msg.user{align-self:flex-end;background:var(--user-bg);color:#fff;border-bottom-right-radius:4px}
42
+ .msg.assistant{align-self:flex-start;background:var(--assistant-bg);border:1px solid var(--border);border-bottom-left-radius:4px}
43
+ .msg.assistant p{margin-bottom:8px}
44
+ .msg.assistant p:last-child{margin-bottom:0}
45
+ .msg.assistant code{background:var(--bg);padding:2px 6px;border-radius:4px;font-size:13px;font-family:'SF Mono',Monaco,Consolas,monospace}
46
+ .msg.assistant pre{background:var(--bg);border:1px solid var(--border);border-radius:8px;padding:12px;overflow-x:auto;margin:8px 0}
47
+ .msg.assistant pre code{background:none;padding:0}
48
+ .msg.assistant ul,.msg.assistant ol{padding-left:20px;margin:8px 0}
49
+ .msg.assistant blockquote{border-left:3px solid var(--accent);padding-left:12px;color:var(--text-muted);margin:8px 0}
50
+ .msg.assistant h1,.msg.assistant h2,.msg.assistant h3,.msg.assistant h4{margin:12px 0 6px}
51
+ .msg.assistant a{color:var(--accent)}
52
+ .msg.assistant table{border-collapse:collapse;margin:8px 0;width:100%}
53
+ .msg.assistant th,.msg.assistant td{border:1px solid var(--border);padding:6px 10px;text-align:left;font-size:13px}
54
+ .msg.assistant th{background:var(--surface)}
55
+ .msg.file-msg{align-self:center;background:var(--surface-alt);border:1px dashed var(--border);color:var(--text-muted);font-size:13px;padding:8px 14px;border-radius:8px}
56
+
57
+ .typing-indicator{align-self:flex-start;padding:12px 20px;background:var(--assistant-bg);border:1px solid var(--border);border-radius:var(--radius);border-bottom-left-radius:4px;display:none;gap:4px;align-items:center}
58
+ .typing-indicator.visible{display:flex}
59
+ .typing-indicator span{width:8px;height:8px;background:var(--text-muted);border-radius:50%;animation:bounce 1.4s infinite both}
60
+ .typing-indicator span:nth-child(2){animation-delay:.2s}
61
+ .typing-indicator span:nth-child(3){animation-delay:.4s}
62
+ @keyframes bounce{0%,80%,100%{transform:scale(.6);opacity:.4}40%{transform:scale(1);opacity:1}}
63
+
64
+ #input-area{padding:12px 20px 20px;background:var(--surface);border-top:1px solid var(--border);flex-shrink:0}
65
+ #input-row{display:flex;gap:8px;align-items:flex-end;max-width:var(--max-w);margin:0 auto}
66
+ #msg-input{flex:1;padding:10px 14px;background:var(--bg);border:1px solid var(--border);border-radius:var(--radius);color:var(--text);font-size:14px;resize:none;max-height:120px;outline:none;font-family:inherit;line-height:1.4}
67
+ #msg-input:focus{border-color:var(--accent)}
68
+ #msg-input::placeholder{color:var(--text-muted)}
69
+ .icon-btn{width:40px;height:40px;border-radius:10px;background:var(--bg);border:1px solid var(--border);color:var(--text-muted);cursor:pointer;display:flex;align-items:center;justify-content:center;transition:all .2s;flex-shrink:0}
70
+ .icon-btn:hover{border-color:var(--accent);color:var(--accent)}
71
+ #send-btn{background:var(--accent-dim);border-color:var(--accent-dim);color:#fff}
72
+ #send-btn:hover{background:var(--accent)}
73
+ #send-btn:disabled{opacity:.4;cursor:not-allowed}
74
+
75
+ #drop-zone{position:fixed;inset:0;z-index:90;background:rgba(13,17,23,.85);display:none;align-items:center;justify-content:center}
76
+ #drop-zone.visible{display:flex}
77
+ .drop-inner{border:2px dashed var(--accent);border-radius:16px;padding:48px;text-align:center;color:var(--accent)}
78
+ .drop-inner span{font-size:48px;display:block;margin-bottom:12px}
79
+ .drop-inner p{font-size:16px}
80
+
81
+ #file-input{display:none}
82
+
83
+ @media(max-width:600px){
84
+ header{padding:10px 14px}
85
+ #messages{padding:12px 10px}
86
+ #input-area{padding:10px 10px 14px}
87
+ .msg{max-width:92%}
88
+ }
89
+ </style>
90
+ </head>
91
+ <body>
92
+
93
+ <div id="auth-overlay">
94
+ <div class="auth-card">
95
+ <h2><span>🦀</span> CF Claw</h2>
96
+ <p>Enter your access token to continue.</p>
97
+ <input type="password" id="auth-input" placeholder="Access token" autocomplete="off">
98
+ <div class="auth-error" id="auth-error">Invalid token. Try again.</div>
99
+ <button id="auth-btn">Connect</button>
100
+ </div>
101
+ </div>
102
+
103
+ <div id="app">
104
+ <header>
105
+ <h1><span>🦀</span> CF Claw</h1>
106
+ <div id="status"><span class="dot"></span> <span id="status-text">Disconnected</span></div>
107
+ </header>
108
+
109
+ <main id="messages"></main>
110
+
111
+ <div id="drop-zone">
112
+ <div class="drop-inner">
113
+ <span>📄</span>
114
+ <p>Drop file here to upload</p>
115
+ </div>
116
+ </div>
117
+
118
+ <div id="input-area">
119
+ <div id="input-row">
120
+ <button class="icon-btn" id="attach-btn" title="Attach file">
121
+ <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21.44 11.05l-9.19 9.19a6 6 0 01-8.49-8.49l9.19-9.19a4 4 0 015.66 5.66l-9.2 9.19a2 2 0 01-2.83-2.83l8.49-8.48"/></svg>
122
+ </button>
123
+ <textarea id="msg-input" rows="1" placeholder="Type a message..."></textarea>
124
+ <button class="icon-btn" id="send-btn" title="Send" disabled>
125
+ <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="22" y1="2" x2="11" y2="13"/><polygon points="22 2 15 22 11 13 2 9 22 2"/></svg>
126
+ </button>
127
+ </div>
128
+ <input type="file" id="file-input">
129
+ </div>
130
+ </div>
131
+
132
+ <script>
133
+ (function(){
134
+ const $ = s => document.querySelector(s);
135
+ const messages = $('#messages');
136
+ const msgInput = $('#msg-input');
137
+ const sendBtn = $('#send-btn');
138
+ const attachBtn = $('#attach-btn');
139
+ const fileInput = $('#file-input');
140
+ const dropZone = $('#drop-zone');
141
+ const authOverlay = $('#auth-overlay');
142
+ const authInput = $('#auth-input');
143
+ const authBtn = $('#auth-btn');
144
+ const authError = $('#auth-error');
145
+ const statusEl = $('#status');
146
+ const statusText = $('#status-text');
147
+
148
+ let ws = null;
149
+ let ready = false;
150
+
151
+ marked.setOptions({ breaks: true, gfm: true });
152
+
153
+ function getToken() {
154
+ return sessionStorage.getItem('cf-claw-token') || '';
155
+ }
156
+
157
+ function setToken(t) {
158
+ sessionStorage.setItem('cf-claw-token', t);
159
+ }
160
+
161
+ function connect() {
162
+ const proto = location.protocol === 'https:' ? 'wss' : 'ws';
163
+ ws = new WebSocket(`${proto}://${location.host}/ws`);
164
+
165
+ ws.onopen = () => {
166
+ const token = getToken();
167
+ if (token || !requiresAuth()) {
168
+ ws.send(JSON.stringify({ type: 'auth', token }));
169
+ }
170
+ };
171
+
172
+ ws.onmessage = (e) => {
173
+ const msg = JSON.parse(e.data);
174
+ handleServerMessage(msg);
175
+ };
176
+
177
+ ws.onclose = () => {
178
+ setStatus(false);
179
+ ready = false;
180
+ setTimeout(connect, 3000);
181
+ };
182
+
183
+ ws.onerror = () => {
184
+ setStatus(false);
185
+ };
186
+ }
187
+
188
+ function requiresAuth() {
189
+ return true;
190
+ }
191
+
192
+ function handleServerMessage(msg) {
193
+ switch(msg.type) {
194
+ case 'auth_ok':
195
+ authOverlay.classList.add('hidden');
196
+ setStatus(true);
197
+ ready = true;
198
+ break;
199
+ case 'auth_error':
200
+ authOverlay.classList.remove('hidden');
201
+ authError.style.display = 'block';
202
+ authError.textContent = msg.reason || 'Authentication failed';
203
+ ready = false;
204
+ if (ws) ws.close();
205
+ break;
206
+ case 'typing':
207
+ showTyping();
208
+ break;
209
+ case 'response':
210
+ hideTyping();
211
+ addMessage('assistant', msg.text);
212
+ break;
213
+ case 'error':
214
+ hideTyping();
215
+ addMessage('assistant', `⚠️ ${msg.message}`);
216
+ break;
217
+ }
218
+ }
219
+
220
+ function setStatus(connected) {
221
+ statusEl.className = connected ? 'connected' : '';
222
+ statusText.textContent = connected ? 'Connected' : 'Disconnected';
223
+ sendBtn.disabled = !connected;
224
+ }
225
+
226
+ function showTyping() {
227
+ let el = messages.querySelector('.typing-indicator');
228
+ if (!el) {
229
+ el = document.createElement('div');
230
+ el.className = 'typing-indicator';
231
+ el.innerHTML = '<span></span><span></span><span></span>';
232
+ messages.appendChild(el);
233
+ }
234
+ el.classList.add('visible');
235
+ scrollToBottom();
236
+ }
237
+
238
+ function hideTyping() {
239
+ const el = messages.querySelector('.typing-indicator');
240
+ if (el) el.classList.remove('visible');
241
+ }
242
+
243
+ function addMessage(role, text) {
244
+ hideTyping();
245
+ const div = document.createElement('div');
246
+ div.className = `msg ${role}`;
247
+ if (role === 'assistant') {
248
+ div.innerHTML = marked.parse(text || '');
249
+ } else {
250
+ div.textContent = text;
251
+ }
252
+ messages.appendChild(div);
253
+ scrollToBottom();
254
+ }
255
+
256
+ function addFileMessage(name) {
257
+ const div = document.createElement('div');
258
+ div.className = 'msg file-msg';
259
+ div.textContent = `📎 ${name}`;
260
+ messages.appendChild(div);
261
+ scrollToBottom();
262
+ }
263
+
264
+ function scrollToBottom() {
265
+ requestAnimationFrame(() => {
266
+ messages.scrollTop = messages.scrollHeight;
267
+ });
268
+ }
269
+
270
+ function sendText(text) {
271
+ if (!ws || !ready || !text.trim()) return;
272
+ ws.send(JSON.stringify({ type: 'message', text }));
273
+ addMessage('user', text);
274
+ msgInput.value = '';
275
+ autoResize();
276
+ }
277
+
278
+ function sendFile(file) {
279
+ if (!ws || !ready) return;
280
+ const reader = new FileReader();
281
+ reader.onload = () => {
282
+ const base64 = reader.result.split(',')[1];
283
+ ws.send(JSON.stringify({
284
+ type: 'file',
285
+ name: file.name,
286
+ mime: file.type,
287
+ data: base64,
288
+ text: ''
289
+ }));
290
+ addFileMessage(file.name);
291
+ };
292
+ reader.readAsDataURL(file);
293
+ }
294
+
295
+ function autoResize() {
296
+ msgInput.style.height = 'auto';
297
+ msgInput.style.height = Math.min(msgInput.scrollHeight, 120) + 'px';
298
+ }
299
+
300
+ sendBtn.addEventListener('click', () => sendText(msgInput.value));
301
+ msgInput.addEventListener('keydown', (e) => {
302
+ if (e.key === 'Enter' && !e.shiftKey) {
303
+ e.preventDefault();
304
+ sendText(msgInput.value);
305
+ }
306
+ });
307
+ msgInput.addEventListener('input', autoResize);
308
+
309
+ attachBtn.addEventListener('click', () => fileInput.click());
310
+ fileInput.addEventListener('change', () => {
311
+ if (fileInput.files.length > 0) {
312
+ sendFile(fileInput.files[0]);
313
+ fileInput.value = '';
314
+ }
315
+ });
316
+
317
+ document.addEventListener('dragover', (e) => { e.preventDefault(); dropZone.classList.add('visible'); });
318
+ document.addEventListener('dragleave', (e) => { if (e.relatedTarget === null) dropZone.classList.remove('visible'); });
319
+ dropZone.addEventListener('dragleave', () => dropZone.classList.remove('visible'));
320
+ dropZone.addEventListener('drop', (e) => {
321
+ e.preventDefault();
322
+ dropZone.classList.remove('visible');
323
+ if (e.dataTransfer.files.length > 0) sendFile(e.dataTransfer.files[0]);
324
+ });
325
+
326
+ authBtn.addEventListener('click', () => {
327
+ const token = authInput.value.trim();
328
+ setToken(token);
329
+ authError.style.display = 'none';
330
+ connect();
331
+ });
332
+ authInput.addEventListener('keydown', (e) => {
333
+ if (e.key === 'Enter') authBtn.click();
334
+ });
335
+
336
+ const saved = getToken();
337
+ if (saved) {
338
+ authOverlay.classList.add('hidden');
339
+ connect();
340
+ }
341
+ })();
342
+ </script>
343
+ </body>
344
+ </html>