@leverageaiapps/theseus-server 1.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 (49) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +165 -0
  3. package/dist/capture.d.ts +3 -0
  4. package/dist/capture.d.ts.map +1 -0
  5. package/dist/capture.js +134 -0
  6. package/dist/capture.js.map +1 -0
  7. package/dist/cloudflare-tunnel.d.ts +9 -0
  8. package/dist/cloudflare-tunnel.d.ts.map +1 -0
  9. package/dist/cloudflare-tunnel.js +218 -0
  10. package/dist/cloudflare-tunnel.js.map +1 -0
  11. package/dist/config.d.ts +7 -0
  12. package/dist/config.d.ts.map +1 -0
  13. package/dist/config.js +84 -0
  14. package/dist/config.js.map +1 -0
  15. package/dist/context-extractor.d.ts +17 -0
  16. package/dist/context-extractor.d.ts.map +1 -0
  17. package/dist/context-extractor.js +118 -0
  18. package/dist/context-extractor.js.map +1 -0
  19. package/dist/index.d.ts +3 -0
  20. package/dist/index.d.ts.map +1 -0
  21. package/dist/index.js +45 -0
  22. package/dist/index.js.map +1 -0
  23. package/dist/pty.d.ts +20 -0
  24. package/dist/pty.d.ts.map +1 -0
  25. package/dist/pty.js +148 -0
  26. package/dist/pty.js.map +1 -0
  27. package/dist/relay.d.ts +5 -0
  28. package/dist/relay.d.ts.map +1 -0
  29. package/dist/relay.js +131 -0
  30. package/dist/relay.js.map +1 -0
  31. package/dist/session.d.ts +5 -0
  32. package/dist/session.d.ts.map +1 -0
  33. package/dist/session.js +257 -0
  34. package/dist/session.js.map +1 -0
  35. package/dist/voice-recognition-modelscope.d.ts +50 -0
  36. package/dist/voice-recognition-modelscope.d.ts.map +1 -0
  37. package/dist/voice-recognition-modelscope.js +171 -0
  38. package/dist/voice-recognition-modelscope.js.map +1 -0
  39. package/dist/web-server.d.ts +6 -0
  40. package/dist/web-server.d.ts.map +1 -0
  41. package/dist/web-server.js +1971 -0
  42. package/dist/web-server.js.map +1 -0
  43. package/package.json +66 -0
  44. package/public/index.html +639 -0
  45. package/public/js/terminal-asr.js +508 -0
  46. package/public/js/terminal.js +514 -0
  47. package/public/js/voice-input.js +422 -0
  48. package/scripts/postinstall.js +66 -0
  49. package/scripts/verify-install.js +124 -0
@@ -0,0 +1,514 @@
1
+ window.term = new Terminal({
2
+ cursorBlink: true,
3
+ fontSize: 13,
4
+ theme: { background: '#0a0a0a', foreground: '#ededed' },
5
+ scrollback: 10000,
6
+ allowTransparency: false,
7
+ });
8
+
9
+ const fitAddon = new FitAddon.FitAddon();
10
+ term.loadAddon(fitAddon);
11
+ term.open(document.getElementById('terminal-container'));
12
+ fitAddon.fit();
13
+
14
+ const statusDot = document.getElementById('status-dot');
15
+ const input = document.getElementById('input');
16
+ const scrollBtn = document.getElementById('scroll-to-bottom');
17
+ const specialKeysBtn = document.getElementById('special-keys-btn');
18
+ const specialKeysPopup = document.getElementById('special-keys-popup');
19
+
20
+ let ws = null;
21
+ let pendingInputs = [];
22
+ let reconnectAttempts = 0;
23
+ const MAX_RECONNECT_ATTEMPTS = 3;
24
+ let reconnectTimeoutId = null;
25
+ let isReconnecting = false;
26
+ let isUserScrolling = false;
27
+
28
+ // Touch scrolling state
29
+ const terminalContainer = document.getElementById('terminal-container');
30
+ const isTouchDevice = 'ontouchstart' in window || navigator.maxTouchPoints > 0;
31
+
32
+ // Initialize touch scrolling for mobile devices
33
+ if (isTouchDevice) {
34
+ initTouchScrolling(terminalContainer, () => { isUserScrolling = true; });
35
+ }
36
+
37
+ // Touch scrolling implementation for smooth mobile scrolling
38
+ function initTouchScrolling(container, onScrollStart) {
39
+ const touchState = {
40
+ startY: 0, lastY: 0, lastTime: 0,
41
+ velocity: 0, identifier: null,
42
+ touching: false, velocityHistory: [],
43
+ accumulator: 0, inertiaId: null
44
+ };
45
+
46
+ // Create touch overlay
47
+ const overlay = createTouchOverlay(container);
48
+
49
+ // Attach event handlers
50
+ overlay.addEventListener('touchstart', handleTouchStart, { passive: false });
51
+ overlay.addEventListener('touchmove', handleTouchMove, { passive: false });
52
+ overlay.addEventListener('touchend', handleTouchEnd, { passive: false });
53
+ overlay.addEventListener('touchcancel', handleTouchCancel, { passive: false });
54
+
55
+ // Prevent conflicts with input area
56
+ const inputArea = document.getElementById('input-area');
57
+ if (inputArea) {
58
+ inputArea.addEventListener('touchstart', e => e.stopPropagation(), { passive: true });
59
+ }
60
+
61
+ function createTouchOverlay(parent) {
62
+ const div = document.createElement('div');
63
+ Object.assign(div.style, {
64
+ position: 'absolute', top: '0', left: '0', right: '0', bottom: '0',
65
+ zIndex: '1', touchAction: 'none', webkitTouchCallout: 'none',
66
+ webkitUserSelect: 'none', userSelect: 'none', pointerEvents: 'auto'
67
+ });
68
+ parent.appendChild(div);
69
+ return div;
70
+ }
71
+
72
+ function performScroll(deltaY) {
73
+ const viewport = container.querySelector('.xterm-viewport');
74
+ if (!viewport) return;
75
+ viewport.scrollTop += deltaY;
76
+ viewport.dispatchEvent(new WheelEvent('wheel', {
77
+ deltaY, deltaMode: 0, bubbles: true, cancelable: true
78
+ }));
79
+ }
80
+
81
+ function handleTouchStart(e) {
82
+ e.preventDefault();
83
+ cancelInertia();
84
+ touchState.accumulator = 0;
85
+
86
+ if (e.touches.length > 0) {
87
+ const touch = e.touches[0];
88
+ Object.assign(touchState, {
89
+ identifier: touch.identifier,
90
+ startY: touch.clientY,
91
+ lastY: touch.clientY,
92
+ lastTime: performance.now(),
93
+ velocity: 0,
94
+ velocityHistory: [],
95
+ touching: true
96
+ });
97
+ onScrollStart();
98
+ }
99
+ }
100
+
101
+ function handleTouchMove(e) {
102
+ e.preventDefault();
103
+ if (!touchState.touching || e.touches.length === 0) return;
104
+
105
+ const touch = findTrackedTouch(e.touches) || e.touches[0];
106
+ const currentY = touch.clientY;
107
+ const deltaY = touchState.lastY - currentY;
108
+ const currentTime = performance.now();
109
+ const timeDelta = Math.max(1, currentTime - touchState.lastTime);
110
+
111
+ // Update velocity
112
+ updateVelocity(deltaY / timeDelta);
113
+
114
+ touchState.lastY = currentY;
115
+ touchState.lastTime = currentTime;
116
+ touchState.accumulator += deltaY;
117
+
118
+ // Apply scroll when threshold reached
119
+ if (Math.abs(touchState.accumulator) >= 0.5) {
120
+ performScroll(touchState.accumulator * 1.8);
121
+ touchState.accumulator = touchState.accumulator % 0.5;
122
+ }
123
+ }
124
+
125
+ function handleTouchEnd(e) {
126
+ e.preventDefault();
127
+ if (!isTouchEnded(e.touches)) return;
128
+
129
+ touchState.touching = false;
130
+ touchState.identifier = null;
131
+
132
+ // Apply remaining scroll
133
+ if (Math.abs(touchState.accumulator) > 0) {
134
+ performScroll(touchState.accumulator * 1.8);
135
+ touchState.accumulator = 0;
136
+ }
137
+
138
+ // Start inertia if needed
139
+ if (Math.abs(touchState.velocity) > 0.01) {
140
+ startInertia();
141
+ }
142
+ }
143
+
144
+ function handleTouchCancel(e) {
145
+ e.preventDefault();
146
+ resetTouchState();
147
+ cancelInertia();
148
+ }
149
+
150
+ function findTrackedTouch(touches) {
151
+ for (let i = 0; i < touches.length; i++) {
152
+ if (touches[i].identifier === touchState.identifier) {
153
+ return touches[i];
154
+ }
155
+ }
156
+ return null;
157
+ }
158
+
159
+ function isTouchEnded(touches) {
160
+ return !findTrackedTouch(touches);
161
+ }
162
+
163
+ function updateVelocity(instant) {
164
+ touchState.velocityHistory.push(instant);
165
+ if (touchState.velocityHistory.length > 5) {
166
+ touchState.velocityHistory.shift();
167
+ }
168
+
169
+ // Calculate weighted average
170
+ let weightedSum = 0, totalWeight = 0;
171
+ touchState.velocityHistory.forEach((v, i) => {
172
+ const weight = i + 1;
173
+ weightedSum += v * weight;
174
+ totalWeight += weight;
175
+ });
176
+ touchState.velocity = totalWeight ? weightedSum / totalWeight : 0;
177
+ }
178
+
179
+ function startInertia() {
180
+ const friction = 0.95;
181
+ const minVelocity = 0.01;
182
+
183
+ function animate() {
184
+ if (Math.abs(touchState.velocity) < minVelocity || touchState.touching) {
185
+ touchState.inertiaId = null;
186
+ touchState.velocity = 0;
187
+ return;
188
+ }
189
+
190
+ performScroll(touchState.velocity * 25);
191
+ touchState.velocity *= friction;
192
+ touchState.inertiaId = requestAnimationFrame(animate);
193
+ }
194
+ animate();
195
+ }
196
+
197
+ function cancelInertia() {
198
+ if (touchState.inertiaId) {
199
+ cancelAnimationFrame(touchState.inertiaId);
200
+ touchState.inertiaId = null;
201
+ }
202
+ }
203
+
204
+ function resetTouchState() {
205
+ Object.assign(touchState, {
206
+ touching: false, identifier: null,
207
+ velocity: 0, velocityHistory: [],
208
+ accumulator: 0
209
+ });
210
+ }
211
+ }
212
+
213
+ function setInputEnabled(enabled) {
214
+ input.disabled = !enabled;
215
+ input.style.opacity = enabled ? '1' : '0.5';
216
+ input.style.cursor = enabled ? 'text' : 'not-allowed';
217
+ if (!enabled) {
218
+ input.placeholder = 'Reconnecting...';
219
+ } else {
220
+ input.placeholder = 'Type command or use voice input...';
221
+ }
222
+ }
223
+
224
+ function updateStatus(state) {
225
+ statusDot.className = '';
226
+ if (state === 'disconnected') {
227
+ statusDot.classList.add('disconnected');
228
+ isReconnecting = true;
229
+ } else if (state === 'connecting') {
230
+ statusDot.classList.add('connecting');
231
+ isReconnecting = true;
232
+ } else if (state === 'connected') {
233
+ isReconnecting = false;
234
+ }
235
+ }
236
+
237
+ function connect() {
238
+ // WebSocket will automatically include cookies with the request
239
+ const wsUrl = location.protocol.replace('http', 'ws') + '//' + location.host + '/ws';
240
+
241
+ // For debugging - log if auth cookie exists
242
+ const hasCookie = document.cookie.includes('auth=');
243
+ console.log('Connecting WebSocket, auth cookie present:', hasCookie);
244
+
245
+ ws = new WebSocket(wsUrl);
246
+
247
+ // Expose WebSocket globally for voice input
248
+ window.terminalWs = ws;
249
+
250
+ ws.onopen = () => {
251
+ console.log('WebSocket connected');
252
+ updateStatus('connected');
253
+ reconnectAttempts = 0;
254
+ fitAddon.fit();
255
+ ws.send(JSON.stringify({ type: 'resize', cols: term.cols, rows: term.rows }));
256
+ };
257
+
258
+ ws.onclose = () => {
259
+ console.log('WebSocket closed');
260
+ updateStatus('disconnected');
261
+ ws = null;
262
+ window.terminalWs = null;
263
+ if (reconnectAttempts < MAX_RECONNECT_ATTEMPTS) {
264
+ reconnectAttempts++;
265
+ reconnectTimeoutId = setTimeout(() => {
266
+ connect();
267
+ }, 500);
268
+ } else {
269
+ setInputEnabled(false);
270
+ input.placeholder = 'Connection failed. Refresh page.';
271
+ }
272
+ };
273
+
274
+ ws.onerror = (err) => {
275
+ console.log('WebSocket error');
276
+ };
277
+
278
+ ws.onmessage = (e) => {
279
+ const msg = JSON.parse(e.data);
280
+ if (msg.type === 'output') {
281
+ term.write(msg.data);
282
+ checkScrollPosition();
283
+ }
284
+ if (msg.type === 'history') {
285
+ term.clear();
286
+ msg.data.forEach(d => term.write(d));
287
+ setInputEnabled(true);
288
+ term.scrollToBottom();
289
+ setTimeout(() => {
290
+ const viewport = document.querySelector('.xterm-viewport');
291
+ if (viewport) {
292
+ viewport.scrollTop = viewport.scrollHeight;
293
+ }
294
+ isUserScrolling = false;
295
+ }, 100);
296
+ }
297
+ // Handle ASR messages
298
+ if (msg.type === 'asr_response') {
299
+ if (window.handleASRResponse) {
300
+ window.handleASRResponse(msg.data);
301
+ }
302
+ }
303
+ // Handle Claude response messages
304
+ if (msg.type === 'claude_response') {
305
+ if (window.voiceInput) {
306
+ if (msg.data.error) {
307
+ console.error('[Claude] Server error:', msg.data.error);
308
+ // Fallback to original transcript if available
309
+ if (msg.data.fallback) {
310
+ // Restore existing content and append fallback
311
+ const existingContent = window.voiceInput.existingInputContent || '';
312
+ const needSpace = existingContent && !existingContent.endsWith(' ');
313
+ input.value = existingContent + (needSpace ? ' ' : '') + msg.data.fallback;
314
+ input.style.height = 'auto';
315
+ input.style.height = input.scrollHeight + 'px';
316
+ }
317
+ } else if (msg.data.text) {
318
+ // First chunk: restore existing content
319
+ if (input.value === '' && window.voiceInput.existingInputContent) {
320
+ const existingContent = window.voiceInput.existingInputContent;
321
+ const needSpace = existingContent && !existingContent.endsWith(' ');
322
+ input.value = existingContent + (needSpace ? ' ' : '');
323
+ // Clear the flag so we don't add it again
324
+ window.voiceInput.existingInputContent = '';
325
+ }
326
+ // Stream text to input
327
+ input.value += msg.data.text;
328
+ input.style.height = 'auto';
329
+ input.style.height = input.scrollHeight + 'px';
330
+ } else if (msg.data.done) {
331
+ console.log('[Claude] Processing complete');
332
+ // Clear the existing content flag
333
+ if (window.voiceInput) {
334
+ window.voiceInput.existingInputContent = '';
335
+
336
+ // Check if auto-submit is pending (triggered by "go go go" command)
337
+ if (window.voiceInput.autoSubmitPending) {
338
+ console.log('[Claude] Auto-submit triggered by "go go go" command');
339
+ window.voiceInput.autoSubmitPending = false;
340
+
341
+ // Auto-submit the command after a short delay to ensure input is updated
342
+ setTimeout(() => {
343
+ const cmd = input.value.trim();
344
+ if (cmd && ws && ws.readyState === 1) {
345
+ input.value = '';
346
+ input.style.height = 'auto';
347
+ // Send text first, then Enter key
348
+ ws.send(JSON.stringify({ type: 'input', data: cmd }));
349
+ setTimeout(() => {
350
+ ws.send(JSON.stringify({ type: 'input', data: String.fromCharCode(13) }));
351
+ }, 50);
352
+ }
353
+ }, 100);
354
+ }
355
+ }
356
+ }
357
+ }
358
+ }
359
+ };
360
+ }
361
+
362
+ // Input handling - must send text and Enter key separately for Claude Code to work
363
+ input.addEventListener('keydown', (e) => {
364
+ if (e.key === 'Enter' && !e.shiftKey) {
365
+ e.preventDefault();
366
+ if (ws && ws.readyState === 1) {
367
+ const cmd = input.value;
368
+ input.value = '';
369
+ input.style.height = 'auto';
370
+ if (cmd) {
371
+ // Send text first, then Enter key separately after delay
372
+ ws.send(JSON.stringify({ type: 'input', data: cmd }));
373
+ setTimeout(() => {
374
+ ws.send(JSON.stringify({ type: 'input', data: String.fromCharCode(13) }));
375
+ }, 50);
376
+ } else {
377
+ // Just send Enter if empty
378
+ ws.send(JSON.stringify({ type: 'input', data: String.fromCharCode(13) }));
379
+ }
380
+ }
381
+ }
382
+ });
383
+
384
+ // Auto-resize textarea
385
+ input.addEventListener('input', () => {
386
+ input.style.height = 'auto';
387
+ input.style.height = input.scrollHeight + 'px';
388
+ });
389
+
390
+ // Special keys handling
391
+ specialKeysBtn.addEventListener('click', (e) => {
392
+ e.stopPropagation();
393
+ specialKeysPopup.classList.toggle('show');
394
+ });
395
+
396
+ document.addEventListener('click', (e) => {
397
+ if (!specialKeysPopup.contains(e.target) && e.target !== specialKeysBtn) {
398
+ specialKeysPopup.classList.remove('show');
399
+ }
400
+ });
401
+
402
+ document.querySelectorAll('.special-key').forEach(btn => {
403
+ btn.addEventListener('click', () => {
404
+ const key = btn.getAttribute('data-key');
405
+ handleSpecialKey(key);
406
+ specialKeysPopup.classList.remove('show');
407
+ });
408
+ });
409
+
410
+ function handleSpecialKey(key) {
411
+ if (!ws || ws.readyState !== 1) return;
412
+
413
+ const keyMap = {
414
+ 'Escape': '\x1b',
415
+ 'Tab': '\t',
416
+ 'Up': '\x1b[A',
417
+ 'Down': '\x1b[B',
418
+ 'Left': '\x1b[D',
419
+ 'Right': '\x1b[C',
420
+ 'Ctrl+C': '\x03',
421
+ 'Ctrl+D': '\x04',
422
+ 'Ctrl+Z': '\x1a',
423
+ 'Ctrl+L': '\x0c',
424
+ 'Home': '\x1b[H',
425
+ 'End': '\x1b[F',
426
+ 'PageUp': '\x1b[5~',
427
+ 'PageDown': '\x1b[6~',
428
+ 'F1': '\x1bOP',
429
+ 'F2': '\x1bOQ',
430
+ 'F3': '\x1bOR',
431
+ 'F4': '\x1bOS'
432
+ };
433
+
434
+ if (keyMap[key]) {
435
+ ws.send(JSON.stringify({ type: 'input', data: keyMap[key] }));
436
+ }
437
+ }
438
+
439
+ // Scroll handling
440
+ function checkScrollPosition() {
441
+ const viewport = document.querySelector('.xterm-viewport');
442
+ if (!viewport) return;
443
+
444
+ const isNearBottom = viewport.scrollHeight - viewport.scrollTop - viewport.clientHeight < 100;
445
+
446
+ if (isNearBottom) {
447
+ scrollBtn.classList.remove('visible');
448
+ isUserScrolling = false;
449
+ } else {
450
+ scrollBtn.classList.add('visible');
451
+ }
452
+ }
453
+
454
+ scrollBtn.addEventListener('click', () => {
455
+ const viewport = document.querySelector('.xterm-viewport');
456
+ if (viewport) {
457
+ viewport.scrollTop = viewport.scrollHeight;
458
+ }
459
+ term.scrollToBottom();
460
+ isUserScrolling = false;
461
+ scrollBtn.classList.remove('visible');
462
+ });
463
+
464
+ // Monitor scroll
465
+ const viewport = document.querySelector('.xterm-viewport');
466
+ if (viewport) {
467
+ viewport.addEventListener('scroll', () => {
468
+ checkScrollPosition();
469
+ });
470
+ }
471
+
472
+ // Window resize
473
+ let resizeTimeout;
474
+ window.addEventListener('resize', () => {
475
+ clearTimeout(resizeTimeout);
476
+ resizeTimeout = setTimeout(() => {
477
+ fitAddon.fit();
478
+ if (ws && ws.readyState === 1) {
479
+ ws.send(JSON.stringify({ type: 'resize', cols: term.cols, rows: term.rows }));
480
+ }
481
+ }, 100);
482
+ });
483
+
484
+ // Debug console controls
485
+ const contextLengthInput = document.getElementById('context-length');
486
+
487
+ // Update context length when changed
488
+ if (contextLengthInput) {
489
+ contextLengthInput.addEventListener('change', () => {
490
+ const length = parseInt(contextLengthInput.value);
491
+ if (window.terminalASR) {
492
+ window.terminalASR.setMaxContextLength(length);
493
+ }
494
+ });
495
+ }
496
+
497
+ // Get terminal context function (kept for other uses)
498
+ function getTerminalContext() {
499
+ if (window.term) {
500
+ const buffer = window.term.buffer.active;
501
+ const lines = [];
502
+ for (let i = Math.max(0, buffer.length - 50); i < buffer.length; i++) {
503
+ const line = buffer.getLine(i);
504
+ if (line) {
505
+ lines.push(line.translateToString(true));
506
+ }
507
+ }
508
+ return lines;
509
+ }
510
+ return [];
511
+ }
512
+
513
+ // Initial connection
514
+ connect();