@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.
- package/LICENSE +21 -0
- package/README.md +165 -0
- package/dist/capture.d.ts +3 -0
- package/dist/capture.d.ts.map +1 -0
- package/dist/capture.js +134 -0
- package/dist/capture.js.map +1 -0
- package/dist/cloudflare-tunnel.d.ts +9 -0
- package/dist/cloudflare-tunnel.d.ts.map +1 -0
- package/dist/cloudflare-tunnel.js +218 -0
- package/dist/cloudflare-tunnel.js.map +1 -0
- package/dist/config.d.ts +7 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +84 -0
- package/dist/config.js.map +1 -0
- package/dist/context-extractor.d.ts +17 -0
- package/dist/context-extractor.d.ts.map +1 -0
- package/dist/context-extractor.js +118 -0
- package/dist/context-extractor.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +45 -0
- package/dist/index.js.map +1 -0
- package/dist/pty.d.ts +20 -0
- package/dist/pty.d.ts.map +1 -0
- package/dist/pty.js +148 -0
- package/dist/pty.js.map +1 -0
- package/dist/relay.d.ts +5 -0
- package/dist/relay.d.ts.map +1 -0
- package/dist/relay.js +131 -0
- package/dist/relay.js.map +1 -0
- package/dist/session.d.ts +5 -0
- package/dist/session.d.ts.map +1 -0
- package/dist/session.js +257 -0
- package/dist/session.js.map +1 -0
- package/dist/voice-recognition-modelscope.d.ts +50 -0
- package/dist/voice-recognition-modelscope.d.ts.map +1 -0
- package/dist/voice-recognition-modelscope.js +171 -0
- package/dist/voice-recognition-modelscope.js.map +1 -0
- package/dist/web-server.d.ts +6 -0
- package/dist/web-server.d.ts.map +1 -0
- package/dist/web-server.js +1971 -0
- package/dist/web-server.js.map +1 -0
- package/package.json +66 -0
- package/public/index.html +639 -0
- package/public/js/terminal-asr.js +508 -0
- package/public/js/terminal.js +514 -0
- package/public/js/voice-input.js +422 -0
- package/scripts/postinstall.js +66 -0
- package/scripts/verify-install.js +124 -0
|
@@ -0,0 +1,422 @@
|
|
|
1
|
+
class VoiceInput {
|
|
2
|
+
constructor() {
|
|
3
|
+
this.voiceBtn = document.getElementById('voice-btn');
|
|
4
|
+
this.input = document.getElementById('input');
|
|
5
|
+
this.transcriptionDiv = document.getElementById('transcription');
|
|
6
|
+
this.contextOverlay = document.getElementById('context-overlay');
|
|
7
|
+
this.contextOverlayContent = document.getElementById('context-overlay-content');
|
|
8
|
+
|
|
9
|
+
this.isRecording = false;
|
|
10
|
+
this.interimTranscript = '';
|
|
11
|
+
this.finalTranscript = '';
|
|
12
|
+
this.collectedTranscript = ''; // Store all collected transcripts
|
|
13
|
+
this.interimTimer = null; // Timer to auto-hide interim transcript after 3 seconds
|
|
14
|
+
this.autoSubmitPending = false; // Flag to auto-submit after Claude processing
|
|
15
|
+
|
|
16
|
+
// Claude API is now handled by the gateway service
|
|
17
|
+
// No need for API keys in the client
|
|
18
|
+
|
|
19
|
+
// Debug flag (will be updated from server)
|
|
20
|
+
this.debugMode = false;
|
|
21
|
+
this.checkDebugMode();
|
|
22
|
+
|
|
23
|
+
this.init();
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Helper function for conditional debug logging
|
|
27
|
+
debugLog(...args) {
|
|
28
|
+
if (this.debugMode) {
|
|
29
|
+
console.log(...args);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Check if debug mode is enabled on server
|
|
34
|
+
async checkDebugMode() {
|
|
35
|
+
try {
|
|
36
|
+
const response = await fetch('/api/terminal-context');
|
|
37
|
+
if (response.ok) {
|
|
38
|
+
const data = await response.json();
|
|
39
|
+
this.debugMode = data.debugAsr || false;
|
|
40
|
+
if (this.debugMode) {
|
|
41
|
+
console.log('[Voice Input] Debug mode enabled');
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
} catch (e) {
|
|
45
|
+
// Ignore errors
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
init() {
|
|
50
|
+
this.voiceBtn.addEventListener('click', () => this.toggleRecording());
|
|
51
|
+
|
|
52
|
+
// Check for browser support
|
|
53
|
+
if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) {
|
|
54
|
+
console.warn('Voice input not supported in this browser');
|
|
55
|
+
this.voiceBtn.style.display = 'none';
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Check Terminal ASR configuration (uses terminal WebSocket -> local server -> gateway)
|
|
60
|
+
if (!window.terminalASR) {
|
|
61
|
+
console.error('Terminal ASR not loaded');
|
|
62
|
+
this.voiceBtn.style.display = 'none';
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
async toggleRecording() {
|
|
68
|
+
if (this.isRecording) {
|
|
69
|
+
await this.stopRecording();
|
|
70
|
+
} else {
|
|
71
|
+
await this.startRecording();
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
async startRecording() {
|
|
76
|
+
try {
|
|
77
|
+
// Terminal ASR uses local server which connects to gateway
|
|
78
|
+
// API keys are configured on the gateway server
|
|
79
|
+
|
|
80
|
+
// Update context from terminal output
|
|
81
|
+
const terminalLines = this.getTerminalContext();
|
|
82
|
+
const context = window.terminalASR.updateContext(terminalLines);
|
|
83
|
+
|
|
84
|
+
// Show loading indicator immediately
|
|
85
|
+
this.showLoadingIndicator();
|
|
86
|
+
|
|
87
|
+
// Clear previous transcripts
|
|
88
|
+
this.interimTranscript = '';
|
|
89
|
+
this.finalTranscript = '';
|
|
90
|
+
this.collectedTranscript = ''; // Reset collected transcript
|
|
91
|
+
this.updateTranscriptionDisplay();
|
|
92
|
+
|
|
93
|
+
// Start real-time recording with Terminal ASR
|
|
94
|
+
await window.terminalASR.startRecording(
|
|
95
|
+
// onPartialResult
|
|
96
|
+
(text) => {
|
|
97
|
+
this.interimTranscript = text;
|
|
98
|
+
this.updateTranscriptionDisplay();
|
|
99
|
+
},
|
|
100
|
+
// onFinalResult
|
|
101
|
+
(text) => {
|
|
102
|
+
// Always collect the transcript first (before any checks)
|
|
103
|
+
// This ensures we don't lose the text when "go go go" is detected
|
|
104
|
+
if (this.finalTranscript) {
|
|
105
|
+
this.finalTranscript += ' ' + text;
|
|
106
|
+
} else {
|
|
107
|
+
this.finalTranscript = text;
|
|
108
|
+
}
|
|
109
|
+
if (this.collectedTranscript) {
|
|
110
|
+
this.collectedTranscript += ' ' + text;
|
|
111
|
+
} else {
|
|
112
|
+
this.collectedTranscript = text;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Clear interim transcript - final result replaces it
|
|
116
|
+
// This matches iOS behavior: interimTranscription = ""
|
|
117
|
+
this.interimTranscript = '';
|
|
118
|
+
|
|
119
|
+
this.updateTranscriptionDisplay();
|
|
120
|
+
|
|
121
|
+
// Check for "go go go" command (case insensitive, with variations)
|
|
122
|
+
const goPattern = /go\s*go\s*go|gogogo/i;
|
|
123
|
+
if (goPattern.test(text)) {
|
|
124
|
+
console.log('[Voice Input] "go go go" command detected!');
|
|
125
|
+
// Set flag to auto-submit after Claude processing
|
|
126
|
+
this.autoSubmitPending = true;
|
|
127
|
+
// Stop recording immediately
|
|
128
|
+
this.stopRecording();
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// DON'T add to input immediately - wait for stop
|
|
133
|
+
// this.addToInput(text); // REMOVED
|
|
134
|
+
},
|
|
135
|
+
// onError - called for errors during recording (not startup errors)
|
|
136
|
+
(error) => {
|
|
137
|
+
console.error('Voice error:', error);
|
|
138
|
+
this.showError(error.message);
|
|
139
|
+
this.stopRecording();
|
|
140
|
+
},
|
|
141
|
+
// onReady - called when ASR session is ready to receive audio
|
|
142
|
+
() => {
|
|
143
|
+
// Update loading indicator to show recording state
|
|
144
|
+
this.transcriptionDiv.innerHTML = `<span style="color: #ef4444">● Recording...</span>`;
|
|
145
|
+
this.transcriptionDiv.style.display = 'block';
|
|
146
|
+
this.transcriptionDiv.classList.add('visible');
|
|
147
|
+
// Show terminal context overlay
|
|
148
|
+
this.showContextOverlay(context);
|
|
149
|
+
}
|
|
150
|
+
);
|
|
151
|
+
|
|
152
|
+
// Update UI - only reached if startRecording succeeded
|
|
153
|
+
this.isRecording = true;
|
|
154
|
+
this.voiceBtn.classList.add('active');
|
|
155
|
+
this.voiceBtn.title = 'Click to stop recording';
|
|
156
|
+
|
|
157
|
+
} catch (error) {
|
|
158
|
+
// Startup error - hide loading indicator and show error
|
|
159
|
+
console.error('Failed to start recording:', error);
|
|
160
|
+
this.hideLoadingIndicator();
|
|
161
|
+
this.showError(error.message);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
async stopRecording() {
|
|
166
|
+
if (!this.isRecording) return;
|
|
167
|
+
|
|
168
|
+
console.log('[Voice Input] Stopping recording...');
|
|
169
|
+
|
|
170
|
+
// Stop recording with Terminal ASR
|
|
171
|
+
await window.terminalASR.stopRecording();
|
|
172
|
+
|
|
173
|
+
// Update UI
|
|
174
|
+
this.isRecording = false;
|
|
175
|
+
this.voiceBtn.classList.remove('active');
|
|
176
|
+
this.voiceBtn.title = 'Voice input (Click to start/stop)';
|
|
177
|
+
|
|
178
|
+
// Hide context overlay
|
|
179
|
+
this.hideContextOverlay();
|
|
180
|
+
|
|
181
|
+
// Clear any pending interim timer
|
|
182
|
+
if (this.interimTimer) {
|
|
183
|
+
clearTimeout(this.interimTimer);
|
|
184
|
+
this.interimTimer = null;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// Debug: Check collected transcript
|
|
188
|
+
console.log('[Voice Input] Collected transcript:', this.collectedTranscript);
|
|
189
|
+
console.log('[Voice Input] Final transcript:', this.finalTranscript);
|
|
190
|
+
|
|
191
|
+
// Get the raw transcription
|
|
192
|
+
const rawText = this.collectedTranscript || this.finalTranscript;
|
|
193
|
+
|
|
194
|
+
if (rawText && rawText.trim()) {
|
|
195
|
+
// First, INSERT (not replace) the raw text into input
|
|
196
|
+
const currentValue = this.input.value;
|
|
197
|
+
if (currentValue) {
|
|
198
|
+
// Insert at cursor position or at end
|
|
199
|
+
const cursorPos = this.input.selectionStart || currentValue.length;
|
|
200
|
+
this.input.value = currentValue.slice(0, cursorPos) + rawText.trim() + ' ' + currentValue.slice(cursorPos);
|
|
201
|
+
} else {
|
|
202
|
+
this.input.value = rawText.trim();
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
console.log('[Voice Input] Inserted raw text:', rawText.trim());
|
|
206
|
+
|
|
207
|
+
// Show loading indicator for Claude correction
|
|
208
|
+
this.transcriptionDiv.innerHTML = `<div class="interim-text">AI optimizing...</div>`;
|
|
209
|
+
this.transcriptionDiv.style.display = 'block';
|
|
210
|
+
|
|
211
|
+
// Request Claude correction via terminal WebSocket -> gateway
|
|
212
|
+
window.terminalASR.requestCorrection(rawText.trim(), (original, corrected) => {
|
|
213
|
+
console.log('[Voice Input] Claude correction received:', corrected);
|
|
214
|
+
|
|
215
|
+
// Replace the inserted raw text with corrected text
|
|
216
|
+
if (this.input.value.includes(original)) {
|
|
217
|
+
this.input.value = this.input.value.replace(original, corrected);
|
|
218
|
+
console.log('[Voice Input] Replaced with corrected text');
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// Hide loading indicator immediately
|
|
222
|
+
this.transcriptionDiv.classList.remove('visible');
|
|
223
|
+
this.transcriptionDiv.innerHTML = '';
|
|
224
|
+
this.transcriptionDiv.style.display = 'none';
|
|
225
|
+
|
|
226
|
+
// Check for auto-submit (go go go command)
|
|
227
|
+
if (this.autoSubmitPending) {
|
|
228
|
+
console.log('[Voice Input] Auto-submit triggered by go go go command');
|
|
229
|
+
this.autoSubmitPending = false;
|
|
230
|
+
|
|
231
|
+
// Remove "go go go" variants from the input text
|
|
232
|
+
const goPattern = /\s*(go\s*go\s*go|gogogo)[。.!!]?\s*/gi;
|
|
233
|
+
this.input.value = this.input.value.replace(goPattern, '').trim();
|
|
234
|
+
|
|
235
|
+
// Trigger enter key press to submit
|
|
236
|
+
if (this.input.value) {
|
|
237
|
+
const enterEvent = new KeyboardEvent('keydown', {
|
|
238
|
+
key: 'Enter',
|
|
239
|
+
code: 'Enter',
|
|
240
|
+
keyCode: 13,
|
|
241
|
+
which: 13,
|
|
242
|
+
bubbles: true
|
|
243
|
+
});
|
|
244
|
+
this.input.dispatchEvent(enterEvent);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// Clear transcription after Claude correction or timeout
|
|
251
|
+
// Don't clear immediately as we need them for correction callback
|
|
252
|
+
setTimeout(() => {
|
|
253
|
+
this.interimTranscript = '';
|
|
254
|
+
this.finalTranscript = '';
|
|
255
|
+
this.collectedTranscript = '';
|
|
256
|
+
// Only update display if not still waiting for correction
|
|
257
|
+
if (!this.transcriptionDiv.innerHTML.includes('AI optimizing')) {
|
|
258
|
+
this.updateTranscriptionDisplay();
|
|
259
|
+
}
|
|
260
|
+
}, 5000);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// Claude processing is now handled by the gateway service
|
|
264
|
+
// The processWithClaude method is no longer needed
|
|
265
|
+
async processWithClaude_DEPRECATED(transcript) {
|
|
266
|
+
this.debugLog('[Claude] Starting to process transcript:', transcript);
|
|
267
|
+
|
|
268
|
+
try {
|
|
269
|
+
// Get terminal context
|
|
270
|
+
const terminalContext = window.terminalASR ? window.terminalASR.terminalContext : '';
|
|
271
|
+
this.debugLog('[Claude] Terminal context length:', terminalContext.length);
|
|
272
|
+
|
|
273
|
+
// Store existing input content to append to later
|
|
274
|
+
this.existingInputContent = this.input.value;
|
|
275
|
+
// Clear input only for the streaming content (will be restored)
|
|
276
|
+
this.input.value = '';
|
|
277
|
+
|
|
278
|
+
// Send to server via WebSocket to process with Claude
|
|
279
|
+
if (window.terminalWs && window.terminalWs.readyState === WebSocket.OPEN) {
|
|
280
|
+
this.debugLog('[Claude] Sending to server via WebSocket');
|
|
281
|
+
console.log('[Claude] Sending to server via WebSocket');
|
|
282
|
+
|
|
283
|
+
// Send Claude processing request
|
|
284
|
+
window.terminalWs.send(JSON.stringify({
|
|
285
|
+
type: 'claude_process',
|
|
286
|
+
transcript: transcript,
|
|
287
|
+
context: terminalContext,
|
|
288
|
+
api_key: this.claudeApiKey,
|
|
289
|
+
model: this.claudeModel
|
|
290
|
+
}));
|
|
291
|
+
|
|
292
|
+
console.log('[Claude] Request sent to server');
|
|
293
|
+
// The server will send back claude_response messages with the streamed text
|
|
294
|
+
// These will be handled in the existing WebSocket message handler
|
|
295
|
+
} else {
|
|
296
|
+
console.error('[Claude] WebSocket not connected:', window.terminalWs ? 'exists but not open' : 'does not exist');
|
|
297
|
+
// Fallback: append the original transcript to existing content
|
|
298
|
+
const existingContent = this.existingInputContent || '';
|
|
299
|
+
const needSpace = existingContent && !existingContent.endsWith(' ');
|
|
300
|
+
this.input.value = existingContent + (needSpace ? ' ' : '') + transcript;
|
|
301
|
+
this.input.style.height = 'auto';
|
|
302
|
+
this.input.style.height = this.input.scrollHeight + 'px';
|
|
303
|
+
}
|
|
304
|
+
} catch (error) {
|
|
305
|
+
console.error('[Claude] Error:', error);
|
|
306
|
+
// Fallback: append the original transcript to existing content
|
|
307
|
+
const existingContent = this.existingInputContent || this.input.value || '';
|
|
308
|
+
const needSpace = existingContent && !existingContent.endsWith(' ');
|
|
309
|
+
this.input.value = existingContent + (needSpace ? ' ' : '') + transcript;
|
|
310
|
+
this.input.style.height = 'auto';
|
|
311
|
+
this.input.style.height = this.input.scrollHeight + 'px';
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
getTerminalContext() {
|
|
316
|
+
// Get terminal lines from xterm.js
|
|
317
|
+
if (window.term) {
|
|
318
|
+
const buffer = window.term.buffer.active;
|
|
319
|
+
const lines = [];
|
|
320
|
+
for (let i = Math.max(0, buffer.length - 50); i < buffer.length; i++) {
|
|
321
|
+
const line = buffer.getLine(i);
|
|
322
|
+
if (line) {
|
|
323
|
+
lines.push(line.translateToString(true));
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
return lines;
|
|
327
|
+
}
|
|
328
|
+
return [];
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
updateTranscriptionDisplay() {
|
|
332
|
+
// Use transcriptionDiv (blue floating dialog) for transcription display
|
|
333
|
+
if (!this.transcriptionDiv) return;
|
|
334
|
+
|
|
335
|
+
// Use collectedTranscript which accumulates ALL final results
|
|
336
|
+
// interimTranscript contains only the current partial (not yet confirmed)
|
|
337
|
+
const hasText = this.interimTranscript || this.collectedTranscript;
|
|
338
|
+
|
|
339
|
+
if (hasText) {
|
|
340
|
+
this.transcriptionDiv.style.display = 'block';
|
|
341
|
+
this.transcriptionDiv.classList.add('visible');
|
|
342
|
+
// Display: [all previous final results] + [current interim]
|
|
343
|
+
// This matches iOS behavior: transcription + interimTranscription
|
|
344
|
+
this.transcriptionDiv.innerHTML = `
|
|
345
|
+
${this.collectedTranscript ? `<strong>${this.collectedTranscript}</strong>` : ''}
|
|
346
|
+
${this.interimTranscript ? `<span style="opacity: 0.7"> ${this.interimTranscript}</span>` : ''}
|
|
347
|
+
`;
|
|
348
|
+
|
|
349
|
+
// Don't auto-hide during recording - let stop handle cleanup
|
|
350
|
+
} else if (!this.isRecording) {
|
|
351
|
+
// Only hide if not recording
|
|
352
|
+
this.transcriptionDiv.classList.remove('visible');
|
|
353
|
+
this.transcriptionDiv.innerHTML = '';
|
|
354
|
+
this.transcriptionDiv.style.display = 'none';
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
addToInput(text) {
|
|
359
|
+
if (this.input && text) {
|
|
360
|
+
// Get current input value
|
|
361
|
+
const currentValue = this.input.value;
|
|
362
|
+
|
|
363
|
+
// Add space if needed
|
|
364
|
+
if (currentValue && !currentValue.endsWith(' ')) {
|
|
365
|
+
this.input.value = currentValue + ' ' + text;
|
|
366
|
+
} else {
|
|
367
|
+
this.input.value = currentValue + text;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
// Trigger input event for any listeners
|
|
371
|
+
this.input.dispatchEvent(new Event('input', { bubbles: true }));
|
|
372
|
+
|
|
373
|
+
// Focus input
|
|
374
|
+
this.input.focus();
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
showError(message) {
|
|
379
|
+
if (this.transcriptionDiv) {
|
|
380
|
+
this.transcriptionDiv.classList.add('visible');
|
|
381
|
+
this.transcriptionDiv.innerHTML = `<span style="color: #ef4444">❌ ${message}</span>`;
|
|
382
|
+
|
|
383
|
+
setTimeout(() => {
|
|
384
|
+
this.transcriptionDiv.classList.remove('visible');
|
|
385
|
+
}, 3000);
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
showLoadingIndicator() {
|
|
390
|
+
// Use transcriptionDiv (blue floating dialog) for loading indicator
|
|
391
|
+
if (this.transcriptionDiv) {
|
|
392
|
+
this.transcriptionDiv.classList.add('visible');
|
|
393
|
+
this.transcriptionDiv.innerHTML = `<span style="color: #60a5fa">🔄 Please wait, initializing voice input...</span>`;
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
hideLoadingIndicator() {
|
|
398
|
+
// Hide the loading indicator
|
|
399
|
+
if (this.transcriptionDiv) {
|
|
400
|
+
this.transcriptionDiv.classList.remove('visible');
|
|
401
|
+
this.transcriptionDiv.innerHTML = '';
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
showContextOverlay(context) {
|
|
406
|
+
if (this.contextOverlay && this.contextOverlayContent) {
|
|
407
|
+
this.contextOverlayContent.textContent = context || 'No context available yet...';
|
|
408
|
+
this.contextOverlay.classList.add('visible');
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
hideContextOverlay() {
|
|
413
|
+
if (this.contextOverlay) {
|
|
414
|
+
this.contextOverlay.classList.remove('visible');
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
// Initialize voice input when page loads
|
|
420
|
+
document.addEventListener('DOMContentLoaded', () => {
|
|
421
|
+
window.voiceInput = new VoiceInput();
|
|
422
|
+
});
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Post-install script to fix node-pty permissions on macOS/Linux
|
|
5
|
+
* This ensures the spawn-helper binary has execute permissions
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const fs = require('fs');
|
|
9
|
+
const path = require('path');
|
|
10
|
+
const os = require('os');
|
|
11
|
+
|
|
12
|
+
const platform = os.platform();
|
|
13
|
+
|
|
14
|
+
// Only run on macOS and Linux
|
|
15
|
+
if (platform !== 'darwin' && platform !== 'linux') {
|
|
16
|
+
console.log('Skipping node-pty permission fix (not macOS/Linux)');
|
|
17
|
+
process.exit(0);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// Determine the architecture
|
|
21
|
+
const arch = os.arch();
|
|
22
|
+
let prebuildDir;
|
|
23
|
+
|
|
24
|
+
if (platform === 'darwin') {
|
|
25
|
+
prebuildDir = arch === 'arm64' ? 'darwin-arm64' : 'darwin-x64';
|
|
26
|
+
} else if (platform === 'linux') {
|
|
27
|
+
prebuildDir = arch === 'arm64' ? 'linux-arm64' : 'linux-x64';
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const spawnHelperPath = path.join(
|
|
31
|
+
__dirname,
|
|
32
|
+
'..',
|
|
33
|
+
'node_modules',
|
|
34
|
+
'node-pty',
|
|
35
|
+
'prebuilds',
|
|
36
|
+
prebuildDir,
|
|
37
|
+
'spawn-helper'
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
// Check if spawn-helper exists
|
|
41
|
+
if (!fs.existsSync(spawnHelperPath)) {
|
|
42
|
+
console.log(`spawn-helper not found at: ${spawnHelperPath}`);
|
|
43
|
+
console.log('This is normal if node-pty uses a different installation method');
|
|
44
|
+
process.exit(0);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
try {
|
|
48
|
+
// Get current permissions
|
|
49
|
+
const stats = fs.statSync(spawnHelperPath);
|
|
50
|
+
const currentMode = stats.mode;
|
|
51
|
+
|
|
52
|
+
// Add execute permission (chmod +x)
|
|
53
|
+
const newMode = currentMode | fs.constants.S_IXUSR | fs.constants.S_IXGRP | fs.constants.S_IXOTH;
|
|
54
|
+
|
|
55
|
+
if (currentMode !== newMode) {
|
|
56
|
+
fs.chmodSync(spawnHelperPath, newMode);
|
|
57
|
+
console.log('✓ Fixed node-pty spawn-helper permissions');
|
|
58
|
+
} else {
|
|
59
|
+
console.log('✓ node-pty spawn-helper permissions already correct');
|
|
60
|
+
}
|
|
61
|
+
} catch (error) {
|
|
62
|
+
console.error('Warning: Failed to fix node-pty permissions:', error.message);
|
|
63
|
+
console.error('You may need to run manually: chmod +x node_modules/node-pty/prebuilds/*/spawn-helper');
|
|
64
|
+
// Don't fail the installation
|
|
65
|
+
process.exit(0);
|
|
66
|
+
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Installation verification script
|
|
5
|
+
* Checks if all dependencies and permissions are correctly set up
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const fs = require('fs');
|
|
9
|
+
const path = require('path');
|
|
10
|
+
const os = require('os');
|
|
11
|
+
const { execSync } = require('child_process');
|
|
12
|
+
|
|
13
|
+
console.log('🔍 Verifying Theseus installation...\n');
|
|
14
|
+
|
|
15
|
+
let hasErrors = false;
|
|
16
|
+
let hasWarnings = false;
|
|
17
|
+
|
|
18
|
+
// Check 1: Node.js version
|
|
19
|
+
console.log('1. Checking Node.js version...');
|
|
20
|
+
const nodeVersion = process.version;
|
|
21
|
+
const majorVersion = parseInt(nodeVersion.slice(1).split('.')[0]);
|
|
22
|
+
if (majorVersion >= 18) {
|
|
23
|
+
console.log(` ✓ Node.js ${nodeVersion} (>= 18.0.0)\n`);
|
|
24
|
+
} else {
|
|
25
|
+
console.log(` ✗ Node.js ${nodeVersion} is too old. Required: >= 18.0.0\n`);
|
|
26
|
+
hasErrors = true;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Check 2: cloudflared
|
|
30
|
+
console.log('2. Checking cloudflared...');
|
|
31
|
+
try {
|
|
32
|
+
const cloudflaredVersion = execSync('cloudflared --version', { encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'] });
|
|
33
|
+
console.log(` ✓ cloudflared installed: ${cloudflaredVersion.trim()}\n`);
|
|
34
|
+
} catch (error) {
|
|
35
|
+
console.log(' ✗ cloudflared not found');
|
|
36
|
+
console.log(' Install it from: https://developers.cloudflare.com/cloudflare-one/connections/connect-apps/install-and-setup/installation/\n');
|
|
37
|
+
hasErrors = true;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Check 3: node-pty installation
|
|
41
|
+
console.log('3. Checking node-pty...');
|
|
42
|
+
const nodePtyPath = path.join(__dirname, '..', 'node_modules', 'node-pty');
|
|
43
|
+
if (fs.existsSync(nodePtyPath)) {
|
|
44
|
+
console.log(' ✓ node-pty installed\n');
|
|
45
|
+
} else {
|
|
46
|
+
console.log(' ✗ node-pty not found. Run: npm install\n');
|
|
47
|
+
hasErrors = true;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Check 4: spawn-helper permissions (macOS/Linux only)
|
|
51
|
+
if (os.platform() === 'darwin' || os.platform() === 'linux') {
|
|
52
|
+
console.log('4. Checking spawn-helper permissions...');
|
|
53
|
+
|
|
54
|
+
const arch = os.arch();
|
|
55
|
+
const platform = os.platform();
|
|
56
|
+
let prebuildDir;
|
|
57
|
+
|
|
58
|
+
if (platform === 'darwin') {
|
|
59
|
+
prebuildDir = arch === 'arm64' ? 'darwin-arm64' : 'darwin-x64';
|
|
60
|
+
} else {
|
|
61
|
+
prebuildDir = arch === 'arm64' ? 'linux-arm64' : 'linux-x64';
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const spawnHelperPath = path.join(nodePtyPath, 'prebuilds', prebuildDir, 'spawn-helper');
|
|
65
|
+
|
|
66
|
+
if (fs.existsSync(spawnHelperPath)) {
|
|
67
|
+
try {
|
|
68
|
+
const stats = fs.statSync(spawnHelperPath);
|
|
69
|
+
const hasExecute = (stats.mode & fs.constants.S_IXUSR) !== 0;
|
|
70
|
+
|
|
71
|
+
if (hasExecute) {
|
|
72
|
+
console.log(' ✓ spawn-helper has execute permissions\n');
|
|
73
|
+
} else {
|
|
74
|
+
console.log(' ✗ spawn-helper missing execute permissions');
|
|
75
|
+
console.log(` Fix: chmod +x ${spawnHelperPath}\n`);
|
|
76
|
+
hasErrors = true;
|
|
77
|
+
}
|
|
78
|
+
} catch (error) {
|
|
79
|
+
console.log(` ⚠ Could not check permissions: ${error.message}\n`);
|
|
80
|
+
hasWarnings = true;
|
|
81
|
+
}
|
|
82
|
+
} else {
|
|
83
|
+
console.log(` ⚠ spawn-helper not found at expected location`);
|
|
84
|
+
console.log(` This may be normal if using a different node-pty version\n`);
|
|
85
|
+
hasWarnings = true;
|
|
86
|
+
}
|
|
87
|
+
} else {
|
|
88
|
+
console.log('4. Skipping spawn-helper check (Windows)\n');
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Check 5: Built files
|
|
92
|
+
console.log('5. Checking built files...');
|
|
93
|
+
const distPath = path.join(__dirname, '..', 'dist', 'index.js');
|
|
94
|
+
if (fs.existsSync(distPath)) {
|
|
95
|
+
console.log(' ✓ Project built successfully\n');
|
|
96
|
+
} else {
|
|
97
|
+
console.log(' ⚠ Project not built yet. Run: npm run build\n');
|
|
98
|
+
hasWarnings = true;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Check 6: Shell availability
|
|
102
|
+
console.log('6. Checking shell...');
|
|
103
|
+
const shell = process.env.SHELL || (os.platform() === 'win32' ? 'cmd.exe' : '/bin/sh');
|
|
104
|
+
if (fs.existsSync(shell)) {
|
|
105
|
+
console.log(` ✓ Shell available: ${shell}\n`);
|
|
106
|
+
} else {
|
|
107
|
+
console.log(` ⚠ Default shell not found: ${shell}\n`);
|
|
108
|
+
hasWarnings = true;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Summary
|
|
112
|
+
console.log('═'.repeat(50));
|
|
113
|
+
if (hasErrors) {
|
|
114
|
+
console.log('❌ Installation has errors. Please fix the issues above.');
|
|
115
|
+
process.exit(1);
|
|
116
|
+
} else if (hasWarnings) {
|
|
117
|
+
console.log('⚠️ Installation complete with warnings.');
|
|
118
|
+
console.log(' You may want to address the warnings above.');
|
|
119
|
+
process.exit(0);
|
|
120
|
+
} else {
|
|
121
|
+
console.log('✅ Installation verified successfully!');
|
|
122
|
+
console.log('\nYou can now run: theseus start');
|
|
123
|
+
process.exit(0);
|
|
124
|
+
}
|