ai-or-die 0.1.49 → 0.1.52
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/bin/ai-or-die.js +1 -1
- package/package.json +3 -3
- package/src/public/app.js +39 -25
- package/src/public/components/extra-keys.css +32 -3
- package/src/public/extra-keys.js +52 -3
- package/src/public/index.html +9 -5
- package/src/public/service-worker.js +42 -5
- package/src/public/session-manager.js +3 -3
- package/src/stt-engine.js +17 -1
- package/src/stt-worker.js +10 -1
- package/CHANGELOG.md +0 -25
- package/scripts/build-sea.js +0 -209
- package/scripts/download-model.js +0 -27
- package/scripts/pty-sea-shim.js +0 -21
- package/scripts/publish-both.sh +0 -21
- package/scripts/release-pr.sh +0 -73
- package/scripts/sherpa-onnx-sea-shim.js +0 -63
- package/scripts/smoke-test-binary.js +0 -195
- package/scripts/update-vendor.sh +0 -6
- package/scripts/validate.ps1 +0 -25
- package/scripts/validate.sh +0 -16
- package/site/ADVANCED_ANALYTICS.md +0 -174
- package/site/docs.html +0 -1021
- package/site/index.html +0 -379
- package/site/script.js +0 -153
- package/site/style.css +0 -370
package/bin/ai-or-die.js
CHANGED
|
@@ -18,7 +18,7 @@ const program = new Command();
|
|
|
18
18
|
program
|
|
19
19
|
.name('ai-or-die')
|
|
20
20
|
.description('ai-or-die — Universal AI coding terminal')
|
|
21
|
-
.version('
|
|
21
|
+
.version(require(path.join(__dirname, '..', 'package.json')).version)
|
|
22
22
|
.option('-p, --port <number>', 'port to run the server on', '7777')
|
|
23
23
|
.option('--no-open', 'do not automatically open browser')
|
|
24
24
|
.option('--auth <token>', 'authentication token for secure access')
|
package/package.json
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ai-or-die",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.52",
|
|
4
4
|
"description": "Universal AI coding terminal — Claude, Copilot, Gemini & more in your browser",
|
|
5
5
|
"main": "src/server.js",
|
|
6
6
|
"bin": {
|
|
7
|
-
"ai-or-die": "bin/ai-or-die.js",
|
|
8
|
-
"aiordie": "bin/ai-or-die.js"
|
|
7
|
+
"ai-or-die": "./bin/ai-or-die.js",
|
|
8
|
+
"aiordie": "./bin/ai-or-die.js"
|
|
9
9
|
},
|
|
10
10
|
"scripts": {
|
|
11
11
|
"start": "node bin/supervisor.js",
|
package/src/public/app.js
CHANGED
|
@@ -16,7 +16,10 @@ class ClaudeCodeWebInterface {
|
|
|
16
16
|
this.currentFolderPath = null;
|
|
17
17
|
this.claudeSessions = [];
|
|
18
18
|
this.isCreatingNewSession = false;
|
|
19
|
-
this
|
|
19
|
+
Object.defineProperty(this, 'isMobile', {
|
|
20
|
+
get: () => this.detectMobile(),
|
|
21
|
+
configurable: true
|
|
22
|
+
});
|
|
20
23
|
this.currentMode = 'chat';
|
|
21
24
|
this._overlayExplicitlyHidden = false;
|
|
22
25
|
this.planDetector = null;
|
|
@@ -250,10 +253,12 @@ class ClaudeCodeWebInterface {
|
|
|
250
253
|
|
|
251
254
|
// Network change handlers
|
|
252
255
|
window.addEventListener('online', () => {
|
|
256
|
+
if (window.feedback) window.feedback.success('Back online — reconnecting...');
|
|
253
257
|
if (this.socket?.readyState !== WebSocket.OPEN) this.reconnect();
|
|
254
258
|
});
|
|
255
259
|
window.addEventListener('offline', () => {
|
|
256
260
|
this.updateStatus('Offline');
|
|
261
|
+
if (window.feedback) window.feedback.warning('Connection lost — you are offline', { duration: 0 });
|
|
257
262
|
});
|
|
258
263
|
|
|
259
264
|
window.addEventListener('resize', () => {
|
|
@@ -344,7 +349,7 @@ class ClaudeCodeWebInterface {
|
|
|
344
349
|
|
|
345
350
|
// Skip preventDefault for scrollable containers that handle their own scroll
|
|
346
351
|
const target = e.target;
|
|
347
|
-
if (target && (target.closest('.xterm-viewport') || target.closest('.modal-body') || target.closest('.extra-keys-bar'))) {
|
|
352
|
+
if (target && (target.closest('.xterm-viewport') || target.closest('.modal-body') || target.closest('.extra-keys-bar') || target.closest('.fb-file-list') || target.closest('.fb-content') || target.closest('.fb-preview-container'))) {
|
|
348
353
|
lastY = y;
|
|
349
354
|
return;
|
|
350
355
|
}
|
|
@@ -725,19 +730,13 @@ class ClaudeCodeWebInterface {
|
|
|
725
730
|
|
|
726
731
|
if (heightDiff > threshold && !this._keyboardOpen) {
|
|
727
732
|
this._keyboardOpen = true;
|
|
733
|
+
// Apply class immediately — CSS transitions handle visual smoothing
|
|
734
|
+
document.body.classList.add('keyboard-open');
|
|
728
735
|
this._adjustTerminalForKeyboard(currentHeight);
|
|
729
|
-
// Debounce body class to prevent flicker during keyboard animation
|
|
730
|
-
clearTimeout(classDebounceTimer);
|
|
731
|
-
classDebounceTimer = setTimeout(() => {
|
|
732
|
-
document.body.classList.add('keyboard-open');
|
|
733
|
-
}, 300);
|
|
734
736
|
} else if (heightDiff <= threshold && this._keyboardOpen) {
|
|
735
737
|
this._keyboardOpen = false;
|
|
738
|
+
document.body.classList.remove('keyboard-open');
|
|
736
739
|
this._restoreTerminalFromKeyboard();
|
|
737
|
-
clearTimeout(classDebounceTimer);
|
|
738
|
-
classDebounceTimer = setTimeout(() => {
|
|
739
|
-
document.body.classList.remove('keyboard-open');
|
|
740
|
-
}, 300);
|
|
741
740
|
}
|
|
742
741
|
};
|
|
743
742
|
|
|
@@ -817,8 +816,10 @@ class ClaudeCodeWebInterface {
|
|
|
817
816
|
document.documentElement.style.setProperty('--visual-viewport-height', availableHeight + 'px');
|
|
818
817
|
const termEl = document.getElementById('terminal');
|
|
819
818
|
if (termEl) {
|
|
819
|
+
// Force reflow after show() so offsetHeight is accurate
|
|
820
820
|
const extraKeysHeight = this.extraKeys?.container?.offsetHeight || 44;
|
|
821
|
-
|
|
821
|
+
void extraKeysHeight; // ensure reflow read is not optimized away
|
|
822
|
+
termEl.style.height = (availableHeight - (this.extraKeys?.container?.offsetHeight || 44)) + 'px';
|
|
822
823
|
if (this.fitAddon) this.fitAddon.fit();
|
|
823
824
|
}
|
|
824
825
|
}
|
|
@@ -1639,12 +1640,12 @@ class ClaudeCodeWebInterface {
|
|
|
1639
1640
|
// but still use backoff to avoid thundering herd
|
|
1640
1641
|
if (this._serverRestarting) {
|
|
1641
1642
|
this.updateStatus('Restarting \u2014 reconnecting\u2026');
|
|
1642
|
-
const restartBackoff = Math.min(2000 * Math.pow(1.5, this._restartReconnectAttempts || 0), 15000);
|
|
1643
|
+
const restartBackoff = Math.min(2000 * Math.pow(1.5, this._restartReconnectAttempts || 0), 15000) * (0.7 + Math.random() * 0.6);
|
|
1643
1644
|
this._restartReconnectAttempts = (this._restartReconnectAttempts || 0) + 1;
|
|
1644
1645
|
setTimeout(() => this.reconnect(), restartBackoff);
|
|
1645
1646
|
} else if (!event.wasClean && this.reconnectAttempts < this.maxReconnectAttempts) {
|
|
1646
|
-
this.updateStatus('Reconnecting...');
|
|
1647
|
-
setTimeout(() => this.reconnect(), Math.min(this.reconnectDelay * Math.pow(2, this.reconnectAttempts), 30000));
|
|
1647
|
+
this.updateStatus('Reconnecting (' + (this.reconnectAttempts + 1) + '/' + this.maxReconnectAttempts + ')...');
|
|
1648
|
+
setTimeout(() => this.reconnect(), Math.min(this.reconnectDelay * Math.pow(2, this.reconnectAttempts), 30000) * (0.7 + Math.random() * 0.6));
|
|
1648
1649
|
this.reconnectAttempts++;
|
|
1649
1650
|
} else {
|
|
1650
1651
|
this.updateStatus('Disconnected');
|
|
@@ -1675,6 +1676,14 @@ class ClaudeCodeWebInterface {
|
|
|
1675
1676
|
clearInterval(this._safariPollInterval);
|
|
1676
1677
|
this._safariPollInterval = null;
|
|
1677
1678
|
}
|
|
1679
|
+
if (this._heartbeatTimer) {
|
|
1680
|
+
clearInterval(this._heartbeatTimer);
|
|
1681
|
+
this._heartbeatTimer = null;
|
|
1682
|
+
}
|
|
1683
|
+
if (this.usageUpdateTimer) {
|
|
1684
|
+
clearInterval(this.usageUpdateTimer);
|
|
1685
|
+
this.usageUpdateTimer = null;
|
|
1686
|
+
}
|
|
1678
1687
|
}
|
|
1679
1688
|
|
|
1680
1689
|
reconnect() {
|
|
@@ -2831,10 +2840,7 @@ class ClaudeCodeWebInterface {
|
|
|
2831
2840
|
activeSocket = resolved.socket;
|
|
2832
2841
|
|
|
2833
2842
|
// Position menu: bottom sheet on mobile, cursor-anchored on desktop
|
|
2834
|
-
|
|
2835
|
-
// so tablet rotation is handled correctly (820px matches CSS breakpoint)
|
|
2836
|
-
const isMobileViewport = window.innerWidth <= 820;
|
|
2837
|
-
if (isMobileViewport) {
|
|
2843
|
+
if (this.isMobile) {
|
|
2838
2844
|
menu.style.left = '';
|
|
2839
2845
|
menu.style.top = '';
|
|
2840
2846
|
menu.style.display = 'block';
|
|
@@ -3322,7 +3328,8 @@ class ClaudeCodeWebInterface {
|
|
|
3322
3328
|
}
|
|
3323
3329
|
|
|
3324
3330
|
startHeartbeat() {
|
|
3325
|
-
|
|
3331
|
+
if (this._heartbeatTimer) clearInterval(this._heartbeatTimer);
|
|
3332
|
+
this._heartbeatTimer = setInterval(() => {
|
|
3326
3333
|
if (this.socket && this.socket.readyState === WebSocket.OPEN) {
|
|
3327
3334
|
this.send({ type: 'ping' });
|
|
3328
3335
|
}
|
|
@@ -4258,7 +4265,14 @@ class ClaudeCodeWebInterface {
|
|
|
4258
4265
|
});
|
|
4259
4266
|
html += '</ul>';
|
|
4260
4267
|
}
|
|
4261
|
-
|
|
4268
|
+
if (typeof DOMPurify !== 'undefined') {
|
|
4269
|
+
return DOMPurify.sanitize(html);
|
|
4270
|
+
} else {
|
|
4271
|
+
// Safe fallback: strip all HTML, render as plain text
|
|
4272
|
+
const temp = document.createElement('div');
|
|
4273
|
+
temp.textContent = html;
|
|
4274
|
+
return temp.innerHTML;
|
|
4275
|
+
}
|
|
4262
4276
|
}
|
|
4263
4277
|
|
|
4264
4278
|
async _loadPlanLibraries() {
|
|
@@ -4492,7 +4506,7 @@ class ClaudeCodeWebInterface {
|
|
|
4492
4506
|
// Container is already visible by default
|
|
4493
4507
|
|
|
4494
4508
|
// Check if mobile screen
|
|
4495
|
-
const isMobile =
|
|
4509
|
+
const isMobile = this.isMobile;
|
|
4496
4510
|
const isSmallMobile = window.innerWidth <= 480;
|
|
4497
4511
|
|
|
4498
4512
|
// Format tokens (K/M notation)
|
|
@@ -4632,8 +4646,8 @@ class ClaudeCodeWebInterface {
|
|
|
4632
4646
|
if (totalTokens > 0) {
|
|
4633
4647
|
const opusPercent = (opusTokens / totalTokens) * 100;
|
|
4634
4648
|
const sonnetPercent = (sonnetTokens / totalTokens) * 100;
|
|
4635
|
-
const isMobile =
|
|
4636
|
-
|
|
4649
|
+
const isMobile = this.isMobile;
|
|
4650
|
+
|
|
4637
4651
|
// Use short names on mobile, full names on desktop
|
|
4638
4652
|
const opusName = isMobile ? 'O' : 'Opus';
|
|
4639
4653
|
const sonnetName = isMobile ? 'S' : 'Sonnet';
|
|
@@ -4655,7 +4669,7 @@ class ClaudeCodeWebInterface {
|
|
|
4655
4669
|
}
|
|
4656
4670
|
} else {
|
|
4657
4671
|
// No active session or expired session - show zeros
|
|
4658
|
-
const isMobile =
|
|
4672
|
+
const isMobile = this.isMobile;
|
|
4659
4673
|
|
|
4660
4674
|
document.getElementById('usageTitle').textContent = '0h 0m';
|
|
4661
4675
|
document.getElementById('usageTokens').textContent = isMobile ? '0%' : '0';
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
.extra-keys-bar {
|
|
2
|
-
display:
|
|
2
|
+
display: flex;
|
|
3
3
|
flex-direction: column;
|
|
4
4
|
position: fixed;
|
|
5
5
|
bottom: 0;
|
|
@@ -8,10 +8,18 @@
|
|
|
8
8
|
background: var(--surface-secondary);
|
|
9
9
|
border-top: 1px solid var(--border-default);
|
|
10
10
|
z-index: var(--z-sticky);
|
|
11
|
+
max-height: 0;
|
|
12
|
+
opacity: 0;
|
|
13
|
+
overflow: hidden;
|
|
14
|
+
transition: max-height 0.15s ease-out, opacity 0.15s ease-out;
|
|
15
|
+
pointer-events: none;
|
|
11
16
|
}
|
|
12
17
|
|
|
13
18
|
.extra-keys-bar.visible {
|
|
14
|
-
|
|
19
|
+
max-height: 120px;
|
|
20
|
+
opacity: 1;
|
|
21
|
+
overflow: visible;
|
|
22
|
+
pointer-events: auto;
|
|
15
23
|
}
|
|
16
24
|
|
|
17
25
|
.extra-keys-row {
|
|
@@ -23,12 +31,19 @@
|
|
|
23
31
|
padding: 2px 8px;
|
|
24
32
|
align-items: center;
|
|
25
33
|
-webkit-overflow-scrolling: touch;
|
|
34
|
+
max-height: 60px;
|
|
35
|
+
opacity: 1;
|
|
36
|
+
transition: max-height 0.15s ease-out, opacity 0.15s ease-out, padding 0.15s ease-out;
|
|
26
37
|
}
|
|
27
38
|
|
|
28
39
|
.extra-keys-row::-webkit-scrollbar { display: none; }
|
|
29
40
|
|
|
30
41
|
.extra-keys-row-hidden {
|
|
31
|
-
|
|
42
|
+
max-height: 0;
|
|
43
|
+
opacity: 0;
|
|
44
|
+
overflow: hidden;
|
|
45
|
+
padding: 0;
|
|
46
|
+
transition: max-height 0.15s ease-out, opacity 0.15s ease-out, padding 0.15s ease-out;
|
|
32
47
|
}
|
|
33
48
|
|
|
34
49
|
.extra-key {
|
|
@@ -70,6 +85,20 @@
|
|
|
70
85
|
opacity: 0.8;
|
|
71
86
|
}
|
|
72
87
|
|
|
88
|
+
.extra-key-clipboard {
|
|
89
|
+
background: var(--surface-tertiary);
|
|
90
|
+
border-color: var(--accent-default);
|
|
91
|
+
border-width: 1.5px;
|
|
92
|
+
color: var(--accent-default);
|
|
93
|
+
font-weight: 600;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
.extra-key-clipboard:active {
|
|
97
|
+
background: var(--accent-soft);
|
|
98
|
+
border-color: var(--accent-default);
|
|
99
|
+
color: var(--text-inverse, var(--text-primary));
|
|
100
|
+
}
|
|
101
|
+
|
|
73
102
|
.extra-key-dismiss:active {
|
|
74
103
|
background: var(--accent-soft);
|
|
75
104
|
border-color: var(--accent-default);
|
package/src/public/extra-keys.js
CHANGED
|
@@ -18,6 +18,8 @@ class ExtraKeys {
|
|
|
18
18
|
this.container.setAttribute('aria-label', 'Terminal modifier keys');
|
|
19
19
|
|
|
20
20
|
const row1Keys = [
|
|
21
|
+
{ label: 'Cp', title: 'Copy selection', handler: 'copy' },
|
|
22
|
+
{ label: 'Pst', title: 'Paste clipboard', handler: 'paste' },
|
|
21
23
|
{ label: 'Tab', data: '\t' },
|
|
22
24
|
{ label: 'Ctrl', modifier: 'ctrl' },
|
|
23
25
|
{ label: 'Alt', modifier: 'alt' },
|
|
@@ -67,6 +69,12 @@ class ExtraKeys {
|
|
|
67
69
|
|
|
68
70
|
this._resizeHandler = () => this._updateRow2Visibility();
|
|
69
71
|
window.addEventListener('resize', this._resizeHandler);
|
|
72
|
+
window.addEventListener('orientationchange', () => {
|
|
73
|
+
setTimeout(() => this._updateRow2Visibility(), 300);
|
|
74
|
+
});
|
|
75
|
+
if (window.visualViewport) {
|
|
76
|
+
window.visualViewport.addEventListener('resize', () => this._updateRow2Visibility());
|
|
77
|
+
}
|
|
70
78
|
}
|
|
71
79
|
|
|
72
80
|
_buildRow(keys) {
|
|
@@ -82,6 +90,16 @@ class ExtraKeys {
|
|
|
82
90
|
if (key.dismiss) {
|
|
83
91
|
btn.classList.add('extra-key-dismiss');
|
|
84
92
|
btn.addEventListener('click', () => this._dismiss());
|
|
93
|
+
} else if (key.handler) {
|
|
94
|
+
btn.classList.add('extra-key-clipboard');
|
|
95
|
+
if (key.title) btn.setAttribute('title', key.title);
|
|
96
|
+
btn.addEventListener('click', () => {
|
|
97
|
+
if (key.handler === 'copy') {
|
|
98
|
+
this._handleCopy();
|
|
99
|
+
} else if (key.handler === 'paste') {
|
|
100
|
+
this._handlePaste();
|
|
101
|
+
}
|
|
102
|
+
});
|
|
85
103
|
} else if (key.modifier) {
|
|
86
104
|
btn.classList.add('extra-key-modifier');
|
|
87
105
|
btn.dataset.modifier = key.modifier;
|
|
@@ -104,6 +122,34 @@ class ExtraKeys {
|
|
|
104
122
|
}
|
|
105
123
|
}
|
|
106
124
|
|
|
125
|
+
async _handleCopy() {
|
|
126
|
+
if ('vibrate' in navigator) try { navigator.vibrate(10); } catch (_) {}
|
|
127
|
+
const selection = this.app && this.app.terminal ? this.app.terminal.getSelection() : '';
|
|
128
|
+
if (!selection) {
|
|
129
|
+
if (window.feedback) window.feedback.warning('No text selected');
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
try {
|
|
133
|
+
await navigator.clipboard.writeText(selection);
|
|
134
|
+
if (window.feedback) window.feedback.success('Copied');
|
|
135
|
+
} catch (err) {
|
|
136
|
+
if (window.feedback) window.feedback.warning('Clipboard access denied');
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
async _handlePaste() {
|
|
141
|
+
if ('vibrate' in navigator) try { navigator.vibrate(10); } catch (_) {}
|
|
142
|
+
try {
|
|
143
|
+
const text = await navigator.clipboard.readText();
|
|
144
|
+
if (text && this.app && this.app.send) {
|
|
145
|
+
this.app.send({ type: 'input', data: text });
|
|
146
|
+
if (window.feedback) window.feedback.success('Pasted');
|
|
147
|
+
}
|
|
148
|
+
} catch (err) {
|
|
149
|
+
if (window.feedback) window.feedback.warning('Clipboard access denied');
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
107
153
|
_sendKey(data) {
|
|
108
154
|
if (!this.app) return;
|
|
109
155
|
if ('vibrate' in navigator) try { navigator.vibrate(10); } catch (_) {}
|
|
@@ -202,9 +248,12 @@ class ExtraKeys {
|
|
|
202
248
|
|
|
203
249
|
_updateRow2Visibility() {
|
|
204
250
|
if (!this._row2 || !this._visible) return;
|
|
205
|
-
const
|
|
206
|
-
|
|
207
|
-
|
|
251
|
+
const vpHeight = window.visualViewport
|
|
252
|
+
? window.visualViewport.height
|
|
253
|
+
: window.innerHeight;
|
|
254
|
+
const isLandscape = window.innerWidth > window.innerHeight;
|
|
255
|
+
const threshold = isLandscape ? 280 : 400;
|
|
256
|
+
if (vpHeight > threshold) {
|
|
208
257
|
this._row2.classList.remove('extra-keys-row-hidden');
|
|
209
258
|
} else {
|
|
210
259
|
this._row2.classList.add('extra-keys-row-hidden');
|
package/src/public/index.html
CHANGED
|
@@ -51,11 +51,15 @@
|
|
|
51
51
|
<link rel="stylesheet" href="components/cards.css">
|
|
52
52
|
<link rel="stylesheet" href="components/menus.css">
|
|
53
53
|
<link rel="stylesheet" href="components/notifications.css">
|
|
54
|
-
<link rel="stylesheet" href="components/file-browser.css">
|
|
55
|
-
<link rel="stylesheet" href="components/
|
|
56
|
-
<link rel="stylesheet" href="components/
|
|
54
|
+
<link rel="stylesheet" href="components/file-browser.css" media="print" onload="this.media='all'">
|
|
55
|
+
<noscript><link rel="stylesheet" href="components/file-browser.css"></noscript>
|
|
56
|
+
<link rel="stylesheet" href="components/banner-base.css" media="print" onload="this.media='all'">
|
|
57
|
+
<noscript><link rel="stylesheet" href="components/banner-base.css"></noscript>
|
|
58
|
+
<link rel="stylesheet" href="components/vscode-tunnel.css" media="print" onload="this.media='all'">
|
|
59
|
+
<noscript><link rel="stylesheet" href="components/vscode-tunnel.css"></noscript>
|
|
57
60
|
<link rel="stylesheet" href="components/feedback.css">
|
|
58
|
-
<link rel="stylesheet" href="components/voice-input.css">
|
|
61
|
+
<link rel="stylesheet" href="components/voice-input.css" media="print" onload="this.media='all'">
|
|
62
|
+
<noscript><link rel="stylesheet" href="components/voice-input.css"></noscript>
|
|
59
63
|
<link rel="stylesheet" href="components/input-overlay.css">
|
|
60
64
|
<link rel="stylesheet" href="components/bottom-nav.css">
|
|
61
65
|
<link rel="stylesheet" href="components/extra-keys.css">
|
|
@@ -64,7 +68,7 @@
|
|
|
64
68
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
65
69
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
66
70
|
<link rel="preconnect" href="https://cdn.jsdelivr.net" crossorigin>
|
|
67
|
-
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600&family=Inter:wght@400;500;600&display=swap" rel="stylesheet">
|
|
71
|
+
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600&family=Inter:wght@400;500;600&display=swap" rel="stylesheet" media="print" onload="this.media='all'">
|
|
68
72
|
<script src="https://unpkg.com/xterm@5.3.0/lib/xterm.js"></script>
|
|
69
73
|
<script src="https://unpkg.com/xterm-addon-fit@0.8.0/lib/xterm-addon-fit.js"></script>
|
|
70
74
|
<script src="https://unpkg.com/xterm-addon-web-links@0.9.0/lib/xterm-addon-web-links.js"></script>
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// Bump this version when urlsToCache entries are added or removed.
|
|
2
2
|
// Content changes to existing files are handled by the network-first fetch strategy.
|
|
3
|
-
const CACHE_NAME = 'ai-or-die-
|
|
3
|
+
const CACHE_NAME = 'ai-or-die-v9';
|
|
4
4
|
const urlsToCache = [
|
|
5
5
|
'/',
|
|
6
6
|
'/index.html',
|
|
@@ -31,19 +31,38 @@ const urlsToCache = [
|
|
|
31
31
|
'/splits.js',
|
|
32
32
|
'/icons.js',
|
|
33
33
|
'/components/extra-keys.css',
|
|
34
|
-
'/
|
|
34
|
+
'/components/file-browser.css',
|
|
35
|
+
'/components/banner-base.css',
|
|
36
|
+
'/components/vscode-tunnel.css',
|
|
37
|
+
'/components/feedback.css',
|
|
38
|
+
'/components/voice-input.css',
|
|
39
|
+
'/components/input-overlay.css',
|
|
40
|
+
'/extra-keys.js',
|
|
41
|
+
'/file-browser.js',
|
|
42
|
+
'/file-editor.js',
|
|
43
|
+
'/voice-handler.js',
|
|
44
|
+
'/image-handler.js',
|
|
45
|
+
'/input-overlay.js',
|
|
46
|
+
'/feedback-manager.js'
|
|
35
47
|
];
|
|
36
48
|
|
|
37
49
|
// Install event - cache resources
|
|
38
50
|
self.addEventListener('install', event => {
|
|
39
51
|
event.waitUntil(
|
|
40
52
|
caches.open(CACHE_NAME)
|
|
41
|
-
.then(cache => {
|
|
53
|
+
.then(async cache => {
|
|
42
54
|
console.log('Opened cache');
|
|
43
|
-
|
|
55
|
+
// Use individual cache.add() calls so one failure doesn't block the rest
|
|
56
|
+
const results = await Promise.allSettled(
|
|
57
|
+
urlsToCache.map(url => cache.add(url))
|
|
58
|
+
);
|
|
59
|
+
const failed = results.filter(r => r.status === 'rejected');
|
|
60
|
+
if (failed.length > 0) {
|
|
61
|
+
console.warn(`Failed to cache ${failed.length} of ${urlsToCache.length} resources`);
|
|
62
|
+
}
|
|
44
63
|
})
|
|
45
64
|
.catch(err => {
|
|
46
|
-
console.error('Failed to cache
|
|
65
|
+
console.error('Failed to open cache:', err);
|
|
47
66
|
})
|
|
48
67
|
);
|
|
49
68
|
});
|
|
@@ -92,6 +111,24 @@ self.addEventListener('fetch', event => {
|
|
|
92
111
|
return;
|
|
93
112
|
}
|
|
94
113
|
|
|
114
|
+
// Cache-first for versioned CDN assets (immutable, pinned to specific versions)
|
|
115
|
+
const knownCDNs = ['unpkg.com', 'cdnjs.cloudflare.com', 'cdn.jsdelivr.net', 'fonts.googleapis.com', 'fonts.gstatic.com'];
|
|
116
|
+
if (knownCDNs.some(cdn => url.hostname.includes(cdn))) {
|
|
117
|
+
event.respondWith(
|
|
118
|
+
caches.match(request).then(cached => {
|
|
119
|
+
if (cached) return cached;
|
|
120
|
+
return fetch(request).then(response => {
|
|
121
|
+
if (response && response.status === 200) {
|
|
122
|
+
const clone = response.clone();
|
|
123
|
+
caches.open(CACHE_NAME).then(cache => cache.put(request, clone));
|
|
124
|
+
}
|
|
125
|
+
return response;
|
|
126
|
+
});
|
|
127
|
+
})
|
|
128
|
+
);
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
|
|
95
132
|
// For static assets, try network first, fall back to cache
|
|
96
133
|
event.respondWith(
|
|
97
134
|
fetch(request)
|
|
@@ -412,7 +412,7 @@ class SessionTabManager {
|
|
|
412
412
|
}
|
|
413
413
|
|
|
414
414
|
updateTabOverflow() {
|
|
415
|
-
const isMobile =
|
|
415
|
+
const isMobile = this.claudeInterface.isMobile;
|
|
416
416
|
const overflowWrapper = document.getElementById('tabOverflowWrapper');
|
|
417
417
|
const overflowCount = document.querySelector('.tab-overflow-count');
|
|
418
418
|
|
|
@@ -580,7 +580,7 @@ class SessionTabManager {
|
|
|
580
580
|
});
|
|
581
581
|
|
|
582
582
|
// Reorder tabs based on the initial timestamps (mobile only)
|
|
583
|
-
if (
|
|
583
|
+
if (this.claudeInterface.isMobile) {
|
|
584
584
|
this.reorderTabsByLastAccessed();
|
|
585
585
|
}
|
|
586
586
|
|
|
@@ -741,7 +741,7 @@ class SessionTabManager {
|
|
|
741
741
|
this.updateTabHistory(sessionId);
|
|
742
742
|
}
|
|
743
743
|
|
|
744
|
-
if (
|
|
744
|
+
if (this.claudeInterface.isMobile) {
|
|
745
745
|
const tabIndex = this.getOrderedTabIds().indexOf(sessionId);
|
|
746
746
|
if (tabIndex >= 2) this.reorderTabsByLastAccessed();
|
|
747
747
|
}
|
package/src/stt-engine.js
CHANGED
|
@@ -21,6 +21,7 @@ class SttEngine {
|
|
|
21
21
|
this._currentRequest = null;
|
|
22
22
|
this._requestIdCounter = 0;
|
|
23
23
|
this._restartAttempts = 0;
|
|
24
|
+
this._lastSpawnError = null;
|
|
24
25
|
this._initPromise = null;
|
|
25
26
|
this._modelManager = new ModelManager({
|
|
26
27
|
modelsDir: options.modelsDir
|
|
@@ -116,7 +117,10 @@ class SttEngine {
|
|
|
116
117
|
}
|
|
117
118
|
|
|
118
119
|
if (msg.type === 'error') {
|
|
119
|
-
console.error('[stt-engine] Worker
|
|
120
|
+
console.error('[stt-engine] Worker error:', msg.message);
|
|
121
|
+
if (msg.message && msg.message.includes('sherpa-onnx-node')) {
|
|
122
|
+
this._lastSpawnError = 'MODULE_NOT_FOUND';
|
|
123
|
+
}
|
|
120
124
|
this._status = 'unavailable';
|
|
121
125
|
return;
|
|
122
126
|
}
|
|
@@ -154,6 +158,13 @@ class SttEngine {
|
|
|
154
158
|
|
|
155
159
|
this._worker = null;
|
|
156
160
|
|
|
161
|
+
// Don't retry if the dependency is fundamentally missing
|
|
162
|
+
if (this._lastSpawnError === 'MODULE_NOT_FOUND') {
|
|
163
|
+
console.error('[stt-engine] sherpa-onnx-node not installed — STT unavailable. Install with: npm install sherpa-onnx-node');
|
|
164
|
+
this._status = 'unavailable';
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
|
|
157
168
|
// Give up after too many consecutive failures
|
|
158
169
|
if (this._restartAttempts >= MAX_RESTART_ATTEMPTS) {
|
|
159
170
|
console.error(`[stt-engine] Max restart attempts (${MAX_RESTART_ATTEMPTS}) reached, giving up`);
|
|
@@ -201,6 +212,7 @@ class SttEngine {
|
|
|
201
212
|
this._worker = worker;
|
|
202
213
|
this._status = 'ready';
|
|
203
214
|
this._restartAttempts = 0;
|
|
215
|
+
this._lastSpawnError = null;
|
|
204
216
|
|
|
205
217
|
worker.on('message', (m) => this._onWorkerMessage(m));
|
|
206
218
|
worker.on('exit', (c) => this._onWorkerExit(c));
|
|
@@ -218,6 +230,10 @@ class SttEngine {
|
|
|
218
230
|
const onError = (err) => {
|
|
219
231
|
worker.off('message', onReady);
|
|
220
232
|
worker.off('error', onError);
|
|
233
|
+
// Tag dependency errors so _onWorkerExit can skip futile retries
|
|
234
|
+
if (err.code === 'MODULE_NOT_FOUND' || (err.message && err.message.includes('sherpa-onnx-node'))) {
|
|
235
|
+
this._lastSpawnError = 'MODULE_NOT_FOUND';
|
|
236
|
+
}
|
|
221
237
|
reject(err);
|
|
222
238
|
};
|
|
223
239
|
|
package/src/stt-worker.js
CHANGED
|
@@ -29,7 +29,16 @@ if (os.platform() === 'win32') {
|
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
// Now safe to require sherpa-onnx-node
|
|
32
|
-
|
|
32
|
+
let OfflineRecognizer;
|
|
33
|
+
try {
|
|
34
|
+
({ OfflineRecognizer } = require('sherpa-onnx-node'));
|
|
35
|
+
} catch (err) {
|
|
36
|
+
parentPort.postMessage({
|
|
37
|
+
type: 'error',
|
|
38
|
+
message: `sherpa-onnx-node is not installed. Install it with: npm install sherpa-onnx-node\n(Original error: ${err.message})`
|
|
39
|
+
});
|
|
40
|
+
process.exit(1);
|
|
41
|
+
}
|
|
33
42
|
|
|
34
43
|
const modelDir = workerData.modelDir;
|
|
35
44
|
const numThreads = workerData.numThreads || Math.min(4, os.cpus().length);
|
package/CHANGELOG.md
DELETED
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
# Changelog
|
|
2
|
-
|
|
3
|
-
All notable changes to this project will be documented in this file.
|
|
4
|
-
|
|
5
|
-
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
|
-
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
-
|
|
8
|
-
## [0.1.0] - 2025-02-06
|
|
9
|
-
|
|
10
|
-
### Added
|
|
11
|
-
- Multi-tool support: Claude, GitHub Copilot, Google Gemini, OpenAI Codex, and raw Terminal
|
|
12
|
-
- BaseBridge architecture for cross-platform CLI tool management
|
|
13
|
-
- Dynamic tool card UI — automatically detects installed tools
|
|
14
|
-
- Microsoft Dev Tunnels integration for secure remote access
|
|
15
|
-
- Real-time terminal streaming via xterm.js and WebSocket
|
|
16
|
-
- Multi-session support with persistent tabs
|
|
17
|
-
- Token-based authentication (auto-generated by default)
|
|
18
|
-
- Session persistence across server restarts
|
|
19
|
-
- Cross-platform support (Linux and Windows via ConPTY)
|
|
20
|
-
- Progressive Web App (PWA) with installable manifest
|
|
21
|
-
- Mobile-responsive design with touch-optimized controls
|
|
22
|
-
- Comprehensive documentation in docs/ (architecture, specs, ADRs, agent instructions)
|
|
23
|
-
- AI agent infrastructure with 5 specialized personas
|
|
24
|
-
- CI/CD pipeline with cross-platform test matrix
|
|
25
|
-
- Validation scripts for Linux and Windows
|