claude-remote 0.4.2 → 0.4.4
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/server.js +77 -39
package/package.json
CHANGED
package/server.js
CHANGED
|
@@ -5,7 +5,7 @@ const os = require('os');
|
|
|
5
5
|
const pty = require('node-pty');
|
|
6
6
|
const { WebSocketServer, WebSocket } = require('ws');
|
|
7
7
|
const crypto = require('crypto');
|
|
8
|
-
const { execSync } = require('child_process');
|
|
8
|
+
const { execSync, spawn } = require('child_process');
|
|
9
9
|
|
|
10
10
|
// --- CLI argument parsing ---
|
|
11
11
|
// Separate bridge args (CWD positional) from claude passthrough flags.
|
|
@@ -194,9 +194,10 @@ let turnState = {
|
|
|
194
194
|
version: 0,
|
|
195
195
|
updatedAt: Date.now(),
|
|
196
196
|
};
|
|
197
|
-
let ttyInputForwarderAttached = false;
|
|
198
|
-
let ttyInputHandler = null;
|
|
199
|
-
let ttyResizeHandler = null;
|
|
197
|
+
let ttyInputForwarderAttached = false;
|
|
198
|
+
let ttyInputHandler = null;
|
|
199
|
+
let ttyResizeHandler = null;
|
|
200
|
+
let activeLinuxClipboardProc = null;
|
|
200
201
|
|
|
201
202
|
// --- Permission approval state ---
|
|
202
203
|
let approvalSeq = 0;
|
|
@@ -218,6 +219,18 @@ function formatTtyInputChunk(chunk) {
|
|
|
218
219
|
const buf = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
219
220
|
return `len=${buf.length} hex=${buf.toString('hex')} base64=${buf.toString('base64')} utf8=${JSON.stringify(buf.toString('utf8'))}`;
|
|
220
221
|
}
|
|
222
|
+
|
|
223
|
+
function clearActiveLinuxClipboardProc(reason = '') {
|
|
224
|
+
if (!activeLinuxClipboardProc) return;
|
|
225
|
+
const { child, tool } = activeLinuxClipboardProc;
|
|
226
|
+
activeLinuxClipboardProc = null;
|
|
227
|
+
try {
|
|
228
|
+
child.kill('SIGTERM');
|
|
229
|
+
log(`Linux clipboard process terminated (${tool}) reason=${reason || 'cleanup'}`);
|
|
230
|
+
} catch (err) {
|
|
231
|
+
log(`Linux clipboard process terminate error (${tool}): ${err.message}`);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
221
234
|
|
|
222
235
|
function wsLabel(ws) {
|
|
223
236
|
const clientId = ws && ws._clientInstanceId ? ` client=${ws._clientInstanceId}` : '';
|
|
@@ -657,10 +670,6 @@ function createTempImageFile(buffer, mediaType, uploadId) {
|
|
|
657
670
|
return tmpFile;
|
|
658
671
|
}
|
|
659
672
|
|
|
660
|
-
function shellQuote(value) {
|
|
661
|
-
return `'${String(value || '').replace(/'/g, `'\\''`)}'`;
|
|
662
|
-
}
|
|
663
|
-
|
|
664
673
|
function getLinuxClipboardTool() {
|
|
665
674
|
if (process.platform === 'win32' || process.platform === 'darwin') return null;
|
|
666
675
|
try {
|
|
@@ -687,6 +696,41 @@ function assertLinuxClipboardAvailable() {
|
|
|
687
696
|
if (tool) return tool;
|
|
688
697
|
throw new Error('Linux image paste requires xclip or wl-copy on the server. Install one and try again.');
|
|
689
698
|
}
|
|
699
|
+
|
|
700
|
+
function startLinuxClipboardImage(tmpFile, mediaType) {
|
|
701
|
+
const type = String(mediaType || 'image/png').toLowerCase();
|
|
702
|
+
const tool = assertLinuxClipboardAvailable();
|
|
703
|
+
const imageBuffer = fs.readFileSync(tmpFile);
|
|
704
|
+
clearActiveLinuxClipboardProc('replace');
|
|
705
|
+
const args = tool === 'xclip'
|
|
706
|
+
? ['-selection', 'clipboard', '-t', type, '-i']
|
|
707
|
+
: ['--type', type];
|
|
708
|
+
const child = spawn(tool, args, {
|
|
709
|
+
detached: true,
|
|
710
|
+
stdio: ['pipe', 'ignore', 'pipe'],
|
|
711
|
+
});
|
|
712
|
+
activeLinuxClipboardProc = { child, tool };
|
|
713
|
+
let stderr = '';
|
|
714
|
+
child.on('error', (err) => {
|
|
715
|
+
log(`Linux clipboard process error (${tool}): ${err.message}`);
|
|
716
|
+
});
|
|
717
|
+
child.stderr.on('data', (chunk) => {
|
|
718
|
+
stderr += chunk.toString('utf8');
|
|
719
|
+
if (stderr.length > 2000) stderr = stderr.slice(-2000);
|
|
720
|
+
});
|
|
721
|
+
child.on('exit', (code, signal) => {
|
|
722
|
+
if (activeLinuxClipboardProc && activeLinuxClipboardProc.child === child) activeLinuxClipboardProc = null;
|
|
723
|
+
const extra = stderr.trim() ? ` stderr=${JSON.stringify(stderr.trim())}` : '';
|
|
724
|
+
log(`Linux clipboard process exited (${tool}) code=${code ?? 'null'} signal=${signal ?? 'null'}${extra}`);
|
|
725
|
+
});
|
|
726
|
+
child.stdin.on('error', (err) => {
|
|
727
|
+
if (err.code !== 'EPIPE') log(`Linux clipboard stdin error (${tool}): ${err.message}`);
|
|
728
|
+
});
|
|
729
|
+
child.stdin.end(imageBuffer);
|
|
730
|
+
child.unref();
|
|
731
|
+
log(`Linux clipboard process started (${tool}) pid=${child.pid ?? 'null'} type=${type} bytes=${imageBuffer.length}`);
|
|
732
|
+
return tool;
|
|
733
|
+
}
|
|
690
734
|
|
|
691
735
|
setInterval(() => {
|
|
692
736
|
const now = Date.now();
|
|
@@ -1689,46 +1733,40 @@ function handlePreparedImageUpload({ tmpFile, mediaType, text, logLabel = '', on
|
|
|
1689
1733
|
} else if (isMac) {
|
|
1690
1734
|
execSync(`osascript -e 'set the clipboard to (read POSIX file "${tmpFile}" as 芦class PNGf禄)'`, { timeout: 10000 });
|
|
1691
1735
|
} else {
|
|
1692
|
-
const
|
|
1693
|
-
|
|
1694
|
-
const quotedPath = shellQuote(tmpFile);
|
|
1695
|
-
const quotedType = shellQuote(type);
|
|
1696
|
-
if (tool === 'xclip') {
|
|
1697
|
-
execSync(`xclip -selection clipboard -t ${quotedType} -i < ${quotedPath}`, {
|
|
1698
|
-
timeout: 10000,
|
|
1699
|
-
shell: '/bin/sh',
|
|
1700
|
-
});
|
|
1701
|
-
} else {
|
|
1702
|
-
execSync(`wl-copy --type ${quotedType} < ${quotedPath}`, {
|
|
1703
|
-
timeout: 10000,
|
|
1704
|
-
shell: '/bin/sh',
|
|
1705
|
-
});
|
|
1706
|
-
}
|
|
1707
|
-
log(`Linux clipboard set with ${tool} (${type})`);
|
|
1736
|
+
const tool = startLinuxClipboardImage(tmpFile, mediaType);
|
|
1737
|
+
log(`Linux clipboard armed with ${tool}`);
|
|
1708
1738
|
}
|
|
1709
1739
|
log('Clipboard set with image');
|
|
1710
1740
|
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1741
|
+
const pasteDelayMs = isWin || isMac ? 0 : 150;
|
|
1742
|
+
setTimeout(() => {
|
|
1743
|
+
if (!claudeProc) return;
|
|
1744
|
+
if (isWin) claudeProc.write('\x1bv');
|
|
1745
|
+
else claudeProc.write('\x16');
|
|
1746
|
+
log('Sent image paste keypress to PTY');
|
|
1747
|
+
}, pasteDelayMs);
|
|
1748
|
+
|
|
1749
|
+
setTimeout(() => {
|
|
1750
|
+
if (!claudeProc) return;
|
|
1751
|
+
const trimmedText = (text || '').trim();
|
|
1752
|
+
if (trimmedText) claudeProc.write(trimmedText);
|
|
1719
1753
|
|
|
1720
1754
|
setTimeout(() => {
|
|
1721
|
-
if (claudeProc) claudeProc.write('\r');
|
|
1722
|
-
log('Sent Enter after image paste' + (trimmedText ? ` + text: "${trimmedText.substring(0, 60)}"` : ''));
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1755
|
+
if (claudeProc) claudeProc.write('\r');
|
|
1756
|
+
log('Sent Enter after image paste' + (trimmedText ? ` + text: "${trimmedText.substring(0, 60)}"` : ''));
|
|
1757
|
+
|
|
1758
|
+
if (!isWin && !isMac) {
|
|
1759
|
+
setTimeout(() => clearActiveLinuxClipboardProc('post-paste'), 1000);
|
|
1760
|
+
}
|
|
1761
|
+
|
|
1762
|
+
setTimeout(() => {
|
|
1763
|
+
if (onCleanup) onCleanup();
|
|
1726
1764
|
else {
|
|
1727
1765
|
try { fs.unlinkSync(tmpFile); } catch {}
|
|
1728
1766
|
}
|
|
1729
1767
|
}, 5000);
|
|
1730
|
-
}, 150);
|
|
1731
|
-
}, 1000);
|
|
1768
|
+
}, 150);
|
|
1769
|
+
}, 1000 + pasteDelayMs);
|
|
1732
1770
|
} catch (err) {
|
|
1733
1771
|
log(`Image upload error: ${err.message}`);
|
|
1734
1772
|
if (onCleanup) onCleanup();
|