@luckydraw/cumulus 0.15.0 → 0.15.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@luckydraw/cumulus",
3
- "version": "0.15.0",
3
+ "version": "0.15.2",
4
4
  "description": "RLM-based CLI chat wrapper for Claude with external history context management",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -32,7 +32,7 @@
32
32
  "cumulus-gateway": "./dist/gateway/daemon.js"
33
33
  },
34
34
  "scripts": {
35
- "build": "tsc && cp -r src/gateway/static dist/gateway/static",
35
+ "build": "tsc && rm -rf dist/gateway/static && cp -r src/gateway/static dist/gateway/static",
36
36
  "dev": "tsc --watch",
37
37
  "lint": "eslint src",
38
38
  "lint:fix": "eslint src --fix",
@@ -1,20 +0,0 @@
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">
6
- <title>Cumulus Chat</title>
7
- <style>
8
- html, body {
9
- margin: 0;
10
- padding: 0;
11
- height: 100%;
12
- background: #1a1a2e;
13
- overflow: hidden;
14
- }
15
- </style>
16
- </head>
17
- <body>
18
- <script src="/widget.js" data-standalone="true" data-api-key=""></script>
19
- </body>
20
- </html>
@@ -1,661 +0,0 @@
1
- /**
2
- * Cumulus Web Chat Widget — self-contained, no external dependencies.
3
- * Embeddable via <script src="/widget.js" data-api-key="..."></script>
4
- * or usable standalone at /chat.
5
- *
6
- * < 50KB unminified.
7
- */
8
- (function () {
9
- 'use strict';
10
-
11
- // ─── Configuration ──────────────────────────────────────────
12
- const script = document.currentScript;
13
- const API_KEY = script?.getAttribute('data-api-key') || '';
14
- const STANDALONE = script?.getAttribute('data-standalone') === 'true';
15
- const WS_URL_OVERRIDE = script?.getAttribute('data-ws-url') || '';
16
-
17
- // Derive WebSocket URL from script src or page location
18
- function getWsUrl() {
19
- if (WS_URL_OVERRIDE) return WS_URL_OVERRIDE;
20
- const loc = window.location;
21
- const proto = loc.protocol === 'https:' ? 'wss:' : 'ws:';
22
- return `${proto}//${loc.host}/ws`;
23
- }
24
-
25
- // Session ID — persisted in localStorage for returning visitors
26
- function getSessionId() {
27
- const key = 'cumulus-session-id';
28
- let id = localStorage.getItem(key);
29
- if (!id) {
30
- id = 'web-' + Math.random().toString(36).slice(2, 10) + Date.now().toString(36);
31
- localStorage.setItem(key, id);
32
- }
33
- return id;
34
- }
35
-
36
- // ─── Styles ─────────────────────────────────────────────────
37
- const STYLES = `
38
- .cumulus-widget * { box-sizing: border-box; margin: 0; padding: 0; }
39
- .cumulus-widget {
40
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
41
- font-size: 14px;
42
- line-height: 1.5;
43
- color: #e0e0e0;
44
- }
45
-
46
- /* Floating bubble (embeddable mode only) */
47
- .cumulus-bubble {
48
- position: fixed;
49
- bottom: 24px;
50
- right: 24px;
51
- width: 56px;
52
- height: 56px;
53
- border-radius: 50%;
54
- background: #6366f1;
55
- border: none;
56
- cursor: pointer;
57
- box-shadow: 0 4px 12px rgba(0,0,0,0.3);
58
- display: flex;
59
- align-items: center;
60
- justify-content: center;
61
- z-index: 10000;
62
- transition: transform 0.15s ease;
63
- }
64
- .cumulus-bubble:hover { transform: scale(1.08); }
65
- .cumulus-bubble svg { width: 28px; height: 28px; fill: white; }
66
-
67
- /* Panel container */
68
- .cumulus-panel {
69
- background: #1a1a2e;
70
- border: 1px solid #2a2a4a;
71
- border-radius: 12px;
72
- display: flex;
73
- flex-direction: column;
74
- overflow: hidden;
75
- }
76
- .cumulus-panel.floating {
77
- position: fixed;
78
- bottom: 92px;
79
- right: 24px;
80
- width: 400px;
81
- height: 560px;
82
- z-index: 10000;
83
- box-shadow: 0 8px 32px rgba(0,0,0,0.5);
84
- }
85
- .cumulus-panel.standalone {
86
- width: 100%;
87
- height: 100vh;
88
- border: none;
89
- border-radius: 0;
90
- }
91
-
92
- /* Header */
93
- .cumulus-header {
94
- display: flex;
95
- align-items: center;
96
- justify-content: space-between;
97
- padding: 12px 16px;
98
- background: #16162b;
99
- border-bottom: 1px solid #2a2a4a;
100
- }
101
- .cumulus-header-title {
102
- font-weight: 600;
103
- font-size: 15px;
104
- color: #f0f0ff;
105
- }
106
- .cumulus-header-status {
107
- font-size: 11px;
108
- display: flex;
109
- align-items: center;
110
- gap: 6px;
111
- }
112
- .cumulus-status-dot {
113
- width: 8px;
114
- height: 8px;
115
- border-radius: 50%;
116
- background: #666;
117
- }
118
- .cumulus-status-dot.connected { background: #22c55e; }
119
- .cumulus-status-dot.connecting { background: #f59e0b; }
120
- .cumulus-status-dot.disconnected { background: #ef4444; }
121
- .cumulus-close-btn {
122
- background: none;
123
- border: none;
124
- color: #888;
125
- cursor: pointer;
126
- font-size: 20px;
127
- padding: 4px;
128
- line-height: 1;
129
- }
130
- .cumulus-close-btn:hover { color: #fff; }
131
-
132
- /* Messages area */
133
- .cumulus-messages {
134
- flex: 1;
135
- overflow-y: auto;
136
- padding: 16px;
137
- display: flex;
138
- flex-direction: column;
139
- gap: 12px;
140
- }
141
- .cumulus-messages::-webkit-scrollbar { width: 6px; }
142
- .cumulus-messages::-webkit-scrollbar-track { background: transparent; }
143
- .cumulus-messages::-webkit-scrollbar-thumb { background: #3a3a5a; border-radius: 3px; }
144
-
145
- /* Message bubbles */
146
- .cumulus-msg {
147
- max-width: 85%;
148
- padding: 10px 14px;
149
- border-radius: 12px;
150
- word-wrap: break-word;
151
- white-space: pre-wrap;
152
- }
153
- .cumulus-msg.user {
154
- align-self: flex-end;
155
- background: #4f46e5;
156
- color: #fff;
157
- border-bottom-right-radius: 4px;
158
- }
159
- .cumulus-msg.assistant {
160
- align-self: flex-start;
161
- background: #2a2a4a;
162
- color: #e0e0e0;
163
- border-bottom-left-radius: 4px;
164
- }
165
-
166
- /* Streaming cursor */
167
- .cumulus-cursor {
168
- display: inline-block;
169
- width: 2px;
170
- height: 1em;
171
- background: #6366f1;
172
- margin-left: 2px;
173
- vertical-align: text-bottom;
174
- animation: cumulus-blink 0.8s step-end infinite;
175
- }
176
- @keyframes cumulus-blink { 50% { opacity: 0; } }
177
-
178
- /* Loading indicator */
179
- .cumulus-loading {
180
- align-self: flex-start;
181
- padding: 10px 14px;
182
- color: #888;
183
- font-style: italic;
184
- }
185
-
186
- /* Markdown basics */
187
- .cumulus-msg code {
188
- background: rgba(255,255,255,0.1);
189
- padding: 2px 6px;
190
- border-radius: 4px;
191
- font-family: 'SF Mono', 'Fira Code', monospace;
192
- font-size: 13px;
193
- }
194
- .cumulus-msg pre {
195
- background: #0d0d1a;
196
- padding: 12px;
197
- border-radius: 8px;
198
- overflow-x: auto;
199
- margin: 8px 0;
200
- }
201
- .cumulus-msg pre code {
202
- background: none;
203
- padding: 0;
204
- display: block;
205
- }
206
- .cumulus-msg a { color: #818cf8; }
207
- .cumulus-msg strong { font-weight: 600; }
208
- .cumulus-msg em { font-style: italic; }
209
-
210
- /* Input area */
211
- .cumulus-input-area {
212
- display: flex;
213
- gap: 8px;
214
- padding: 12px 16px;
215
- background: #16162b;
216
- border-top: 1px solid #2a2a4a;
217
- }
218
- .cumulus-input {
219
- flex: 1;
220
- background: #1a1a2e;
221
- border: 1px solid #3a3a5a;
222
- border-radius: 8px;
223
- color: #e0e0e0;
224
- padding: 10px 12px;
225
- font-size: 14px;
226
- font-family: inherit;
227
- resize: none;
228
- min-height: 40px;
229
- max-height: 120px;
230
- outline: none;
231
- }
232
- .cumulus-input:focus { border-color: #6366f1; }
233
- .cumulus-input::placeholder { color: #666; }
234
- .cumulus-send-btn {
235
- background: #6366f1;
236
- border: none;
237
- border-radius: 8px;
238
- color: white;
239
- padding: 0 16px;
240
- cursor: pointer;
241
- font-size: 14px;
242
- font-weight: 500;
243
- white-space: nowrap;
244
- }
245
- .cumulus-send-btn:hover { background: #5558e6; }
246
- .cumulus-send-btn:disabled { opacity: 0.5; cursor: not-allowed; }
247
-
248
- /* Empty state */
249
- .cumulus-empty {
250
- display: flex;
251
- align-items: center;
252
- justify-content: center;
253
- flex: 1;
254
- color: #555;
255
- font-size: 15px;
256
- text-align: center;
257
- padding: 20px;
258
- }
259
- `;
260
-
261
- // ─── Simple Markdown Renderer ───────────────────────────────
262
- function renderMarkdown(text) {
263
- let html = escapeHtml(text);
264
-
265
- // Code blocks (``` ... ```)
266
- html = html.replace(/```(\w*)\n([\s\S]*?)```/g, function (_, lang, code) {
267
- return '<pre><code>' + code + '</code></pre>';
268
- });
269
-
270
- // Inline code
271
- html = html.replace(/`([^`]+)`/g, '<code>$1</code>');
272
-
273
- // Bold
274
- html = html.replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>');
275
-
276
- // Italic
277
- html = html.replace(/\*(.+?)\*/g, '<em>$1</em>');
278
-
279
- // Links
280
- html = html.replace(
281
- /\[([^\]]+)\]\(([^)]+)\)/g,
282
- '<a href="$2" target="_blank" rel="noopener">$1</a>'
283
- );
284
-
285
- // Line breaks
286
- html = html.replace(/\n/g, '<br>');
287
-
288
- return html;
289
- }
290
-
291
- function escapeHtml(text) {
292
- const div = document.createElement('div');
293
- div.textContent = text;
294
- return div.innerHTML;
295
- }
296
-
297
- // ─── WebSocket Connection ───────────────────────────────────
298
- function createConnection(opts) {
299
- const { wsUrl, apiKey, sessionId, onMessage, onStatus } = opts;
300
- let ws = null;
301
- let reconnectTimer = null;
302
- let reconnectDelay = 1000;
303
-
304
- function connect() {
305
- onStatus('connecting');
306
- ws = new WebSocket(wsUrl);
307
-
308
- ws.onopen = function () {
309
- onStatus('connected');
310
- reconnectDelay = 1000;
311
-
312
- // Authenticate
313
- ws.send(JSON.stringify({ type: 'auth', apiKey: apiKey }));
314
-
315
- // Load history
316
- ws.send(
317
- JSON.stringify({
318
- type: 'history',
319
- threadName: sessionId,
320
- limit: 50,
321
- })
322
- );
323
- };
324
-
325
- ws.onmessage = function (event) {
326
- try {
327
- var data = JSON.parse(event.data);
328
- onMessage(data);
329
- } catch (e) {
330
- console.error('[Cumulus] Invalid message:', e);
331
- }
332
- };
333
-
334
- ws.onclose = function () {
335
- onStatus('disconnected');
336
- scheduleReconnect();
337
- };
338
-
339
- ws.onerror = function () {
340
- // onclose will fire after this
341
- };
342
- }
343
-
344
- function scheduleReconnect() {
345
- if (reconnectTimer) return;
346
- reconnectTimer = setTimeout(function () {
347
- reconnectTimer = null;
348
- reconnectDelay = Math.min(reconnectDelay * 1.5, 30000);
349
- connect();
350
- }, reconnectDelay);
351
- }
352
-
353
- function send(data) {
354
- if (ws && ws.readyState === WebSocket.OPEN) {
355
- ws.send(JSON.stringify(data));
356
- }
357
- }
358
-
359
- function close() {
360
- if (reconnectTimer) {
361
- clearTimeout(reconnectTimer);
362
- reconnectTimer = null;
363
- }
364
- if (ws) {
365
- ws.onclose = null; // Prevent reconnect
366
- ws.close();
367
- ws = null;
368
- }
369
- }
370
-
371
- connect();
372
- return { send: send, close: close };
373
- }
374
-
375
- // ─── Widget UI ──────────────────────────────────────────────
376
- function createWidget(opts) {
377
- const { standalone } = opts;
378
- const sessionId = getSessionId();
379
- const wsUrl = getWsUrl();
380
-
381
- // Inject styles
382
- const styleEl = document.createElement('style');
383
- styleEl.textContent = STYLES;
384
- document.head.appendChild(styleEl);
385
-
386
- // State
387
- var messages = [];
388
- var streaming = false;
389
- var streamBuffer = '';
390
- var connection = null;
391
- var connectionStatus = 'disconnected';
392
-
393
- // ─── DOM elements ──────────────────────────────
394
- const container = document.createElement('div');
395
- container.className = 'cumulus-widget';
396
-
397
- // Build panel
398
- const panel = document.createElement('div');
399
- panel.className = 'cumulus-panel ' + (standalone ? 'standalone' : 'floating');
400
- panel.style.display = standalone ? 'flex' : 'none';
401
-
402
- // Header
403
- const header = document.createElement('div');
404
- header.className = 'cumulus-header';
405
- header.innerHTML =
406
- '<span class="cumulus-header-title">Cumulus</span>' +
407
- '<span class="cumulus-header-status">' +
408
- '<span class="cumulus-status-dot" data-testid="webchat-status-dot"></span>' +
409
- '<span data-testid="webchat-status-text">Disconnected</span>' +
410
- '</span>' +
411
- (standalone
412
- ? ''
413
- : '<button class="cumulus-close-btn" data-testid="webchat-close">&times;</button>');
414
- panel.appendChild(header);
415
-
416
- // Messages area
417
- const messagesEl = document.createElement('div');
418
- messagesEl.className = 'cumulus-messages';
419
- messagesEl.setAttribute('data-testid', 'webchat-messages');
420
- messagesEl.innerHTML = '<div class="cumulus-empty">Send a message to start chatting</div>';
421
- panel.appendChild(messagesEl);
422
-
423
- // Input area
424
- const inputArea = document.createElement('div');
425
- inputArea.className = 'cumulus-input-area';
426
- const input = document.createElement('textarea');
427
- input.className = 'cumulus-input';
428
- input.setAttribute('data-testid', 'webchat-input');
429
- input.placeholder = 'Type a message...';
430
- input.rows = 1;
431
- const sendBtn = document.createElement('button');
432
- sendBtn.className = 'cumulus-send-btn';
433
- sendBtn.setAttribute('data-testid', 'webchat-send');
434
- sendBtn.textContent = 'Send';
435
- inputArea.appendChild(input);
436
- inputArea.appendChild(sendBtn);
437
- panel.appendChild(inputArea);
438
-
439
- container.appendChild(panel);
440
-
441
- // Floating bubble (embeddable mode only)
442
- var bubble = null;
443
- if (!standalone) {
444
- bubble = document.createElement('button');
445
- bubble.className = 'cumulus-bubble';
446
- bubble.setAttribute('data-testid', 'webchat-bubble');
447
- bubble.innerHTML =
448
- '<svg viewBox="0 0 24 24"><path d="M20 2H4c-1.1 0-2 .9-2 2v18l4-4h14c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm0 14H6l-2 2V4h16v12z"/></svg>';
449
- container.appendChild(bubble);
450
- }
451
-
452
- // ─── Rendering ─────────────────────────────────
453
- function scrollToBottom() {
454
- messagesEl.scrollTop = messagesEl.scrollHeight;
455
- }
456
-
457
- function renderMessages() {
458
- messagesEl.innerHTML = '';
459
-
460
- if (messages.length === 0 && !streaming) {
461
- messagesEl.innerHTML = '<div class="cumulus-empty">Send a message to start chatting</div>';
462
- return;
463
- }
464
-
465
- for (var i = 0; i < messages.length; i++) {
466
- var msg = messages[i];
467
- var el = document.createElement('div');
468
- el.className = 'cumulus-msg ' + msg.role;
469
- if (msg.role === 'assistant') {
470
- el.innerHTML = renderMarkdown(msg.content);
471
- } else {
472
- el.textContent = msg.content;
473
- }
474
- messagesEl.appendChild(el);
475
- }
476
-
477
- // Streaming indicator
478
- if (streaming) {
479
- var streamEl = document.createElement('div');
480
- streamEl.className = 'cumulus-msg assistant';
481
- streamEl.setAttribute('data-testid', 'webchat-streaming');
482
- if (streamBuffer) {
483
- streamEl.innerHTML =
484
- renderMarkdown(streamBuffer) + '<span class="cumulus-cursor"></span>';
485
- } else {
486
- streamEl.innerHTML =
487
- '<span style="color:#888;font-style:italic">Thinking...</span><span class="cumulus-cursor"></span>';
488
- }
489
- messagesEl.appendChild(streamEl);
490
- }
491
-
492
- scrollToBottom();
493
- }
494
-
495
- function updateStatus(status) {
496
- connectionStatus = status;
497
- var dot = panel.querySelector('.cumulus-status-dot');
498
- var text = panel.querySelector('[data-testid="webchat-status-text"]');
499
- if (dot) {
500
- dot.className = 'cumulus-status-dot ' + status;
501
- }
502
- if (text) {
503
- text.textContent = status.charAt(0).toUpperCase() + status.slice(1);
504
- }
505
- }
506
-
507
- // ─── Message handling ──────────────────────────
508
- function handleServerMessage(data) {
509
- switch (data.type) {
510
- case 'auth_ok':
511
- break;
512
-
513
- case 'auth_error':
514
- console.error('[Cumulus] Auth failed:', data.error);
515
- break;
516
-
517
- case 'history':
518
- if (data.messages && data.messages.length > 0) {
519
- messages = data.messages.map(function (m) {
520
- return { role: m.role, content: m.content };
521
- });
522
- renderMessages();
523
- }
524
- break;
525
-
526
- case 'token':
527
- if (!streaming) {
528
- streaming = true;
529
- streamBuffer = '';
530
- }
531
- streamBuffer += data.text;
532
- renderMessages();
533
- break;
534
-
535
- case 'segment':
536
- // Tool use/result — could show in verbose mode later
537
- break;
538
-
539
- case 'done':
540
- streaming = false;
541
- messages.push({ role: 'assistant', content: data.response || streamBuffer });
542
- streamBuffer = '';
543
- renderMessages();
544
- break;
545
-
546
- case 'error':
547
- streaming = false;
548
- if (streamBuffer) {
549
- messages.push({ role: 'assistant', content: streamBuffer + '\n\n[Error: ' + data.error + ']' });
550
- } else {
551
- messages.push({ role: 'assistant', content: '[Error: ' + data.error + ']' });
552
- }
553
- streamBuffer = '';
554
- renderMessages();
555
- break;
556
- }
557
- }
558
-
559
- function sendMessage() {
560
- var text = input.value.trim();
561
- if (!text || streaming) return;
562
-
563
- messages.push({ role: 'user', content: text });
564
- input.value = '';
565
- input.style.height = 'auto';
566
- streaming = true;
567
- streamBuffer = '';
568
- renderMessages();
569
-
570
- connection.send({
571
- type: 'message',
572
- threadName: sessionId,
573
- message: text,
574
- });
575
- }
576
-
577
- // ─── Event listeners ───────────────────────────
578
- sendBtn.addEventListener('click', sendMessage);
579
-
580
- input.addEventListener('keydown', function (e) {
581
- if (e.key === 'Enter' && !e.shiftKey) {
582
- e.preventDefault();
583
- sendMessage();
584
- }
585
- });
586
-
587
- // Auto-resize textarea
588
- input.addEventListener('input', function () {
589
- input.style.height = 'auto';
590
- input.style.height = Math.min(input.scrollHeight, 120) + 'px';
591
- });
592
-
593
- // Bubble toggle (embeddable mode)
594
- if (bubble) {
595
- bubble.addEventListener('click', function () {
596
- var showing = panel.style.display !== 'none';
597
- panel.style.display = showing ? 'none' : 'flex';
598
- if (!showing && !connection) {
599
- startConnection();
600
- }
601
- if (!showing) {
602
- input.focus();
603
- scrollToBottom();
604
- }
605
- });
606
-
607
- // Close button
608
- var closeBtn = panel.querySelector('.cumulus-close-btn');
609
- if (closeBtn) {
610
- closeBtn.addEventListener('click', function () {
611
- panel.style.display = 'none';
612
- });
613
- }
614
- }
615
-
616
- // ─── Connect ───────────────────────────────────
617
- function startConnection() {
618
- connection = createConnection({
619
- wsUrl: wsUrl,
620
- apiKey: API_KEY,
621
- sessionId: sessionId,
622
- onMessage: handleServerMessage,
623
- onStatus: updateStatus,
624
- });
625
- }
626
-
627
- // ─── Mount ─────────────────────────────────────
628
- function mount(target) {
629
- (target || document.body).appendChild(container);
630
- if (standalone) {
631
- startConnection();
632
- input.focus();
633
- }
634
- }
635
-
636
- function destroy() {
637
- if (connection) connection.close();
638
- container.remove();
639
- }
640
-
641
- return { mount: mount, destroy: destroy, send: sendMessage };
642
- }
643
-
644
- // ─── Auto-mount ─────────────────────────────────────────────
645
- if (script) {
646
- var widget = createWidget({
647
- standalone: STANDALONE,
648
- });
649
-
650
- if (document.readyState === 'loading') {
651
- document.addEventListener('DOMContentLoaded', function () {
652
- widget.mount();
653
- });
654
- } else {
655
- widget.mount();
656
- }
657
-
658
- // Expose for programmatic access
659
- window.CumulusChat = widget;
660
- }
661
- })();