ai-or-die 0.1.13 → 0.1.14
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 +1 -1
- package/src/base-bridge.js +17 -8
- package/src/copilot-bridge.js +2 -1
- package/src/gemini-bridge.js +2 -1
- package/src/public/app.js +79 -4
- package/src/public/index.html +12 -0
- package/src/public/style.css +31 -0
- package/src/server.js +24 -17
package/package.json
CHANGED
package/src/base-bridge.js
CHANGED
|
@@ -198,20 +198,17 @@ class BaseBridge {
|
|
|
198
198
|
|
|
199
199
|
const env = {
|
|
200
200
|
...process.env,
|
|
201
|
-
TERM:
|
|
202
|
-
FORCE_COLOR: '1'
|
|
201
|
+
TERM: 'xterm-256color',
|
|
202
|
+
FORCE_COLOR: '1',
|
|
203
|
+
COLORTERM: 'truecolor'
|
|
203
204
|
};
|
|
204
|
-
if (!this.isWindows) {
|
|
205
|
-
env.COLORTERM = 'truecolor';
|
|
206
|
-
}
|
|
207
205
|
|
|
208
206
|
const ptyProcess = spawn(this.command, args, {
|
|
209
207
|
cwd: workingDir,
|
|
210
208
|
env,
|
|
211
209
|
cols,
|
|
212
210
|
rows,
|
|
213
|
-
name:
|
|
214
|
-
useConpty: this.isWindows
|
|
211
|
+
name: 'xterm-256color'
|
|
215
212
|
});
|
|
216
213
|
|
|
217
214
|
const session = {
|
|
@@ -238,6 +235,8 @@ class BaseBridge {
|
|
|
238
235
|
}, SPAWN_TIMEOUT_MS);
|
|
239
236
|
|
|
240
237
|
let dataBuffer = '';
|
|
238
|
+
let outputBatch = '';
|
|
239
|
+
let flushTimer = null;
|
|
241
240
|
|
|
242
241
|
ptyProcess.onData((data) => {
|
|
243
242
|
if (!receivedLifeSign) {
|
|
@@ -258,7 +257,17 @@ class BaseBridge {
|
|
|
258
257
|
dataBuffer = dataBuffer.slice(-5000);
|
|
259
258
|
}
|
|
260
259
|
|
|
261
|
-
|
|
260
|
+
// Batch output: coalesce PTY data chunks from the same I/O cycle
|
|
261
|
+
// setImmediate flushes on the next tick — no arbitrary time boundary
|
|
262
|
+
// that could split ANSI escape sequences or multi-byte UTF-8 characters
|
|
263
|
+
outputBatch += data;
|
|
264
|
+
if (!flushTimer) {
|
|
265
|
+
flushTimer = setImmediate(() => {
|
|
266
|
+
onOutput(outputBatch);
|
|
267
|
+
outputBatch = '';
|
|
268
|
+
flushTimer = null;
|
|
269
|
+
});
|
|
270
|
+
}
|
|
262
271
|
});
|
|
263
272
|
|
|
264
273
|
ptyProcess.onExit((exitCode, signal) => {
|
package/src/copilot-bridge.js
CHANGED
package/src/gemini-bridge.js
CHANGED
package/src/public/app.js
CHANGED
|
@@ -338,6 +338,7 @@ class ClaudeCodeWebInterface {
|
|
|
338
338
|
|
|
339
339
|
this.terminal.open(document.getElementById('terminal'));
|
|
340
340
|
this.fitTerminal();
|
|
341
|
+
this.setupTerminalContextMenu();
|
|
341
342
|
|
|
342
343
|
this.terminal.onData((data) => {
|
|
343
344
|
if (this.socket && this.socket.readyState === WebSocket.OPEN) {
|
|
@@ -912,7 +913,12 @@ class ClaudeCodeWebInterface {
|
|
|
912
913
|
}
|
|
913
914
|
}, 45000);
|
|
914
915
|
|
|
915
|
-
this.send({
|
|
916
|
+
this.send({
|
|
917
|
+
type: `start_${toolId}`,
|
|
918
|
+
options,
|
|
919
|
+
cols: this.terminal ? this.terminal.cols : 80,
|
|
920
|
+
rows: this.terminal ? this.terminal.rows : 24
|
|
921
|
+
});
|
|
916
922
|
}
|
|
917
923
|
|
|
918
924
|
clearTerminal() {
|
|
@@ -937,7 +943,13 @@ class ClaudeCodeWebInterface {
|
|
|
937
943
|
if (this.fitAddon) {
|
|
938
944
|
try {
|
|
939
945
|
this.fitAddon.fit();
|
|
940
|
-
|
|
946
|
+
|
|
947
|
+
// Subtract 2 rows to account for tab bar / header not included in fit calculation
|
|
948
|
+
const adjustedRows = Math.max(1, this.terminal.rows - 2);
|
|
949
|
+
if (adjustedRows !== this.terminal.rows) {
|
|
950
|
+
this.terminal.resize(this.terminal.cols, adjustedRows);
|
|
951
|
+
}
|
|
952
|
+
|
|
941
953
|
// On mobile, ensure terminal doesn't exceed viewport width
|
|
942
954
|
if (this.isMobile) {
|
|
943
955
|
const terminalElement = document.querySelector('.xterm');
|
|
@@ -959,6 +971,66 @@ class ClaudeCodeWebInterface {
|
|
|
959
971
|
}
|
|
960
972
|
}
|
|
961
973
|
|
|
974
|
+
setupTerminalContextMenu() {
|
|
975
|
+
const menu = document.getElementById('termContextMenu');
|
|
976
|
+
if (!menu) return;
|
|
977
|
+
|
|
978
|
+
const termEl = document.getElementById('terminal');
|
|
979
|
+
termEl.addEventListener('contextmenu', (e) => {
|
|
980
|
+
e.preventDefault();
|
|
981
|
+
e.stopPropagation();
|
|
982
|
+
|
|
983
|
+
// Position menu at cursor
|
|
984
|
+
menu.style.left = e.clientX + 'px';
|
|
985
|
+
menu.style.top = e.clientY + 'px';
|
|
986
|
+
menu.style.display = 'block';
|
|
987
|
+
|
|
988
|
+
// Disable copy if no selection
|
|
989
|
+
const copyItem = menu.querySelector('[data-action="copy"]');
|
|
990
|
+
if (copyItem) {
|
|
991
|
+
const hasSelection = this.terminal.hasSelection();
|
|
992
|
+
copyItem.classList.toggle('disabled', !hasSelection);
|
|
993
|
+
}
|
|
994
|
+
});
|
|
995
|
+
|
|
996
|
+
// Handle menu item clicks
|
|
997
|
+
menu.addEventListener('click', async (e) => {
|
|
998
|
+
const action = e.target.dataset.action;
|
|
999
|
+
if (!action) return;
|
|
1000
|
+
menu.style.display = 'none';
|
|
1001
|
+
|
|
1002
|
+
switch (action) {
|
|
1003
|
+
case 'copy': {
|
|
1004
|
+
const sel = this.terminal.getSelection();
|
|
1005
|
+
if (sel) await navigator.clipboard.writeText(sel);
|
|
1006
|
+
break;
|
|
1007
|
+
}
|
|
1008
|
+
case 'paste': {
|
|
1009
|
+
try {
|
|
1010
|
+
const text = await navigator.clipboard.readText();
|
|
1011
|
+
if (text && this.socket && this.socket.readyState === WebSocket.OPEN) {
|
|
1012
|
+
this.send({ type: 'input', data: text });
|
|
1013
|
+
}
|
|
1014
|
+
} catch { /* clipboard permission denied */ }
|
|
1015
|
+
break;
|
|
1016
|
+
}
|
|
1017
|
+
case 'selectAll':
|
|
1018
|
+
this.terminal.selectAll();
|
|
1019
|
+
break;
|
|
1020
|
+
case 'clear':
|
|
1021
|
+
this.terminal.clear();
|
|
1022
|
+
break;
|
|
1023
|
+
}
|
|
1024
|
+
this.terminal.focus();
|
|
1025
|
+
});
|
|
1026
|
+
|
|
1027
|
+
// Dismiss menu on click outside or scroll
|
|
1028
|
+
document.addEventListener('click', (e) => {
|
|
1029
|
+
if (!menu.contains(e.target)) menu.style.display = 'none';
|
|
1030
|
+
});
|
|
1031
|
+
document.addEventListener('keydown', () => { menu.style.display = 'none'; });
|
|
1032
|
+
}
|
|
1033
|
+
|
|
962
1034
|
updateStatus(status) {
|
|
963
1035
|
// Status display removed with header - status now shown in tabs
|
|
964
1036
|
console.log('Status:', status);
|
|
@@ -1011,6 +1083,7 @@ class ClaudeCodeWebInterface {
|
|
|
1011
1083
|
const themeSelect = document.getElementById('themeSelect');
|
|
1012
1084
|
if (themeSelect) themeSelect.value = settings.theme === 'light' ? 'light' : 'dark';
|
|
1013
1085
|
document.getElementById('showTokenStats').checked = settings.showTokenStats;
|
|
1086
|
+
document.getElementById('dangerousMode').checked = settings.dangerousMode || false;
|
|
1014
1087
|
}
|
|
1015
1088
|
|
|
1016
1089
|
hideSettings() {
|
|
@@ -1026,7 +1099,8 @@ class ClaudeCodeWebInterface {
|
|
|
1026
1099
|
const defaults = {
|
|
1027
1100
|
fontSize: 14,
|
|
1028
1101
|
showTokenStats: true,
|
|
1029
|
-
theme: 'dark'
|
|
1102
|
+
theme: 'dark',
|
|
1103
|
+
dangerousMode: false
|
|
1030
1104
|
};
|
|
1031
1105
|
|
|
1032
1106
|
try {
|
|
@@ -1042,7 +1116,8 @@ class ClaudeCodeWebInterface {
|
|
|
1042
1116
|
const settings = {
|
|
1043
1117
|
fontSize: parseInt(document.getElementById('fontSize').value),
|
|
1044
1118
|
showTokenStats: document.getElementById('showTokenStats').checked,
|
|
1045
|
-
theme: (document.getElementById('themeSelect')?.value) || 'dark'
|
|
1119
|
+
theme: (document.getElementById('themeSelect')?.value) || 'dark',
|
|
1120
|
+
dangerousMode: document.getElementById('dangerousMode').checked
|
|
1046
1121
|
};
|
|
1047
1122
|
|
|
1048
1123
|
try {
|
package/src/public/index.html
CHANGED
|
@@ -107,6 +107,13 @@
|
|
|
107
107
|
<div class="terminal-container" id="terminalContainer" data-view="single">
|
|
108
108
|
<div class="terminal-wrapper">
|
|
109
109
|
<div id="terminal"></div>
|
|
110
|
+
<div id="termContextMenu" class="term-context-menu" style="display:none">
|
|
111
|
+
<div class="ctx-item" data-action="copy">Copy</div>
|
|
112
|
+
<div class="ctx-item" data-action="paste">Paste</div>
|
|
113
|
+
<div class="ctx-sep"></div>
|
|
114
|
+
<div class="ctx-item" data-action="selectAll">Select All</div>
|
|
115
|
+
<div class="ctx-item" data-action="clear">Clear</div>
|
|
116
|
+
</div>
|
|
110
117
|
</div>
|
|
111
118
|
</div>
|
|
112
119
|
</main>
|
|
@@ -156,6 +163,11 @@
|
|
|
156
163
|
<label for="showTokenStats">Show Token Stats:</label>
|
|
157
164
|
<input type="checkbox" id="showTokenStats" checked>
|
|
158
165
|
</div>
|
|
166
|
+
<div class="setting-group">
|
|
167
|
+
<label for="dangerousMode">Autonomous Mode:</label>
|
|
168
|
+
<input type="checkbox" id="dangerousMode">
|
|
169
|
+
<small style="display:block;color:#e8a838;margin-top:4px">Skips permission prompts for Claude, Copilot, Gemini, Codex. Use in trusted environments only.</small>
|
|
170
|
+
</div>
|
|
159
171
|
</div>
|
|
160
172
|
<div class="modal-footer">
|
|
161
173
|
<button class="btn btn-primary" id="saveSettingsBtn">Save Settings</button>
|
package/src/public/style.css
CHANGED
|
@@ -1260,6 +1260,37 @@ body {
|
|
|
1260
1260
|
background: var(--border-hover);
|
|
1261
1261
|
}
|
|
1262
1262
|
|
|
1263
|
+
/* Terminal context menu */
|
|
1264
|
+
.term-context-menu {
|
|
1265
|
+
position: fixed;
|
|
1266
|
+
z-index: 1000;
|
|
1267
|
+
background: var(--bg-secondary);
|
|
1268
|
+
border: 1px solid var(--border);
|
|
1269
|
+
border-radius: 8px;
|
|
1270
|
+
padding: 4px 0;
|
|
1271
|
+
min-width: 140px;
|
|
1272
|
+
box-shadow: 0 8px 24px rgba(0,0,0,.4);
|
|
1273
|
+
}
|
|
1274
|
+
.ctx-item {
|
|
1275
|
+
padding: 6px 16px;
|
|
1276
|
+
cursor: pointer;
|
|
1277
|
+
font-size: 13px;
|
|
1278
|
+
color: var(--text-primary);
|
|
1279
|
+
}
|
|
1280
|
+
.ctx-item:hover {
|
|
1281
|
+
background: var(--accent);
|
|
1282
|
+
color: #fff;
|
|
1283
|
+
}
|
|
1284
|
+
.ctx-item.disabled {
|
|
1285
|
+
opacity: .4;
|
|
1286
|
+
pointer-events: none;
|
|
1287
|
+
}
|
|
1288
|
+
.ctx-sep {
|
|
1289
|
+
height: 1px;
|
|
1290
|
+
background: var(--border);
|
|
1291
|
+
margin: 4px 0;
|
|
1292
|
+
}
|
|
1293
|
+
|
|
1263
1294
|
/* Folder Browser Modal */
|
|
1264
1295
|
.folder-browser-modal {
|
|
1265
1296
|
display: none;
|
package/src/server.js
CHANGED
|
@@ -94,9 +94,7 @@ class ClaudeCodeWebServer {
|
|
|
94
94
|
}
|
|
95
95
|
|
|
96
96
|
async saveSessionsToDisk() {
|
|
97
|
-
|
|
98
|
-
await this.sessionStore.saveSessions(this.claudeSessions);
|
|
99
|
-
}
|
|
97
|
+
await this.sessionStore.saveSessions(this.claudeSessions);
|
|
100
98
|
}
|
|
101
99
|
|
|
102
100
|
async handleShutdown() {
|
|
@@ -345,7 +343,7 @@ class ClaudeCodeWebServer {
|
|
|
345
343
|
});
|
|
346
344
|
|
|
347
345
|
// Delete a Claude session
|
|
348
|
-
this.app.delete('/api/sessions/:sessionId', (req, res) => {
|
|
346
|
+
this.app.delete('/api/sessions/:sessionId', async (req, res) => {
|
|
349
347
|
const sessionId = req.params.sessionId;
|
|
350
348
|
const session = this.claudeSessions.get(sessionId);
|
|
351
349
|
|
|
@@ -374,10 +372,10 @@ class ClaudeCodeWebServer {
|
|
|
374
372
|
});
|
|
375
373
|
|
|
376
374
|
this.claudeSessions.delete(sessionId);
|
|
377
|
-
|
|
378
|
-
// Save sessions after deletion
|
|
379
|
-
this.saveSessionsToDisk();
|
|
380
|
-
|
|
375
|
+
|
|
376
|
+
// Save sessions after deletion — await to ensure persistence
|
|
377
|
+
await this.saveSessionsToDisk();
|
|
378
|
+
|
|
381
379
|
res.json({ success: true, message: 'Session deleted' });
|
|
382
380
|
});
|
|
383
381
|
|
|
@@ -390,8 +388,8 @@ class ClaudeCodeWebServer {
|
|
|
390
388
|
tools: {
|
|
391
389
|
claude: { alias: this.aliases.claude, available: this.claudeBridge.isAvailable(), hasDangerousMode: true },
|
|
392
390
|
codex: { alias: this.aliases.codex, available: this.codexBridge.isAvailable(), hasDangerousMode: true },
|
|
393
|
-
copilot: { alias: this.aliases.copilot, available: this.copilotBridge.isAvailable(), hasDangerousMode:
|
|
394
|
-
gemini: { alias: this.aliases.gemini, available: this.geminiBridge.isAvailable(), hasDangerousMode:
|
|
391
|
+
copilot: { alias: this.aliases.copilot, available: this.copilotBridge.isAvailable(), hasDangerousMode: true },
|
|
392
|
+
gemini: { alias: this.aliases.gemini, available: this.geminiBridge.isAvailable(), hasDangerousMode: true },
|
|
395
393
|
terminal: { alias: this.aliases.terminal, available: this.terminalBridge.isAvailable(), hasDangerousMode: false }
|
|
396
394
|
}
|
|
397
395
|
});
|
|
@@ -645,8 +643,15 @@ class ClaudeCodeWebServer {
|
|
|
645
643
|
server = http.createServer(this.app);
|
|
646
644
|
}
|
|
647
645
|
|
|
648
|
-
this.wss = new WebSocket.Server({
|
|
646
|
+
this.wss = new WebSocket.Server({
|
|
649
647
|
server,
|
|
648
|
+
perMessageDeflate: {
|
|
649
|
+
serverNoContextTakeover: true,
|
|
650
|
+
clientNoContextTakeover: true,
|
|
651
|
+
serverMaxWindowBits: 13,
|
|
652
|
+
clientMaxWindowBits: 13,
|
|
653
|
+
zlibDeflateOptions: { level: 6 }
|
|
654
|
+
},
|
|
650
655
|
verifyClient: (info) => {
|
|
651
656
|
if (!this.noAuth && this.auth) {
|
|
652
657
|
const url = new URL(info.req.url, 'ws://localhost');
|
|
@@ -753,19 +758,19 @@ class ClaudeCodeWebServer {
|
|
|
753
758
|
break;
|
|
754
759
|
|
|
755
760
|
case 'start_claude':
|
|
756
|
-
await this.startToolSession(wsId, 'claude', this.claudeBridge, data.options || {});
|
|
761
|
+
await this.startToolSession(wsId, 'claude', this.claudeBridge, data.options || {}, data.cols, data.rows);
|
|
757
762
|
break;
|
|
758
763
|
case 'start_codex':
|
|
759
|
-
await this.startToolSession(wsId, 'codex', this.codexBridge, data.options || {});
|
|
764
|
+
await this.startToolSession(wsId, 'codex', this.codexBridge, data.options || {}, data.cols, data.rows);
|
|
760
765
|
break;
|
|
761
766
|
case 'start_copilot':
|
|
762
|
-
await this.startToolSession(wsId, 'copilot', this.copilotBridge, data.options || {});
|
|
767
|
+
await this.startToolSession(wsId, 'copilot', this.copilotBridge, data.options || {}, data.cols, data.rows);
|
|
763
768
|
break;
|
|
764
769
|
case 'start_gemini':
|
|
765
|
-
await this.startToolSession(wsId, 'gemini', this.geminiBridge, data.options || {});
|
|
770
|
+
await this.startToolSession(wsId, 'gemini', this.geminiBridge, data.options || {}, data.cols, data.rows);
|
|
766
771
|
break;
|
|
767
772
|
case 'start_terminal':
|
|
768
|
-
await this.startToolSession(wsId, 'terminal', this.terminalBridge, data.options || {});
|
|
773
|
+
await this.startToolSession(wsId, 'terminal', this.terminalBridge, data.options || {}, data.cols, data.rows);
|
|
769
774
|
break;
|
|
770
775
|
|
|
771
776
|
case 'input':
|
|
@@ -969,7 +974,7 @@ class ClaudeCodeWebServer {
|
|
|
969
974
|
return bridges[agentType] || null;
|
|
970
975
|
}
|
|
971
976
|
|
|
972
|
-
async startToolSession(wsId, toolName, bridge, options) {
|
|
977
|
+
async startToolSession(wsId, toolName, bridge, options, cols, rows) {
|
|
973
978
|
const wsInfo = this.webSocketConnections.get(wsId);
|
|
974
979
|
if (!wsInfo) {
|
|
975
980
|
console.warn(`startToolSession(${toolName}): wsInfo not found for wsId=${wsId}`);
|
|
@@ -1021,6 +1026,8 @@ class ClaudeCodeWebServer {
|
|
|
1021
1026
|
console.log(`startToolSession(${toolName}): spawning in session ${sessionId}, workingDir=${session.workingDir}`);
|
|
1022
1027
|
await bridge.startSession(sessionId, {
|
|
1023
1028
|
workingDir: session.workingDir,
|
|
1029
|
+
cols: cols || 80,
|
|
1030
|
+
rows: rows || 24,
|
|
1024
1031
|
onOutput: (data) => {
|
|
1025
1032
|
const currentSession = this.claudeSessions.get(sessionId);
|
|
1026
1033
|
if (!currentSession) return;
|