claude-remote 0.4.1 → 0.4.3
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 +76 -45
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.
|
|
@@ -158,11 +158,12 @@ let CWD = _parsedCwd;
|
|
|
158
158
|
const CLAUDE_HOME = path.join(os.homedir(), '.claude');
|
|
159
159
|
const CLAUDE_STATE_FILE = path.join(os.homedir(), '.claude.json');
|
|
160
160
|
const PROJECTS_DIR = path.join(CLAUDE_HOME, 'projects');
|
|
161
|
-
const AUTH_HELLO_TIMEOUT_MS = 5000;
|
|
162
|
-
const WS_CLOSE_AUTH_FAILED = 4001;
|
|
163
|
-
const WS_CLOSE_AUTH_TIMEOUT = 4002;
|
|
164
|
-
const WS_CLOSE_REASON_AUTH_FAILED = 'auth_failed';
|
|
165
|
-
const WS_CLOSE_REASON_AUTH_TIMEOUT = 'auth_timeout';
|
|
161
|
+
const AUTH_HELLO_TIMEOUT_MS = 5000;
|
|
162
|
+
const WS_CLOSE_AUTH_FAILED = 4001;
|
|
163
|
+
const WS_CLOSE_AUTH_TIMEOUT = 4002;
|
|
164
|
+
const WS_CLOSE_REASON_AUTH_FAILED = 'auth_failed';
|
|
165
|
+
const WS_CLOSE_REASON_AUTH_TIMEOUT = 'auth_timeout';
|
|
166
|
+
const DEBUG_TTY_INPUT = process.env.CLAUDE_REMOTE_DEBUG_TTY_INPUT === '1';
|
|
166
167
|
|
|
167
168
|
// --- State ---
|
|
168
169
|
let claudeProc = null;
|
|
@@ -208,10 +209,15 @@ const PARTIAL_AUTO_ALLOW = new Set(['Read', 'Glob', 'Grep', 'Write', 'Edit']);
|
|
|
208
209
|
// --- Logging → file only (never pollute the terminal) ---
|
|
209
210
|
const LOG_FILE = path.join(os.homedir(), '.claude', 'bridge.log');
|
|
210
211
|
fs.writeFileSync(LOG_FILE, `--- Bridge started ${new Date().toISOString()} ---\n`);
|
|
211
|
-
function log(msg) {
|
|
212
|
-
const line = `[${new Date().toISOString()}] ${msg}\n`;
|
|
213
|
-
fs.appendFileSync(LOG_FILE, line);
|
|
214
|
-
}
|
|
212
|
+
function log(msg) {
|
|
213
|
+
const line = `[${new Date().toISOString()}] ${msg}\n`;
|
|
214
|
+
fs.appendFileSync(LOG_FILE, line);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function formatTtyInputChunk(chunk) {
|
|
218
|
+
const buf = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
219
|
+
return `len=${buf.length} hex=${buf.toString('hex')} base64=${buf.toString('base64')} utf8=${JSON.stringify(buf.toString('utf8'))}`;
|
|
220
|
+
}
|
|
215
221
|
|
|
216
222
|
function wsLabel(ws) {
|
|
217
223
|
const clientId = ws && ws._clientInstanceId ? ` client=${ws._clientInstanceId}` : '';
|
|
@@ -273,11 +279,18 @@ function setTurnState(phase, { sessionId = currentSessionId, reason = '', force
|
|
|
273
279
|
}
|
|
274
280
|
|
|
275
281
|
function attachTtyForwarders() {
|
|
276
|
-
if (!isTTY || ttyInputForwarderAttached) return;
|
|
277
|
-
|
|
278
|
-
ttyInputHandler = (chunk) => {
|
|
279
|
-
if (
|
|
280
|
-
|
|
282
|
+
if (!isTTY || ttyInputForwarderAttached) return;
|
|
283
|
+
|
|
284
|
+
ttyInputHandler = (chunk) => {
|
|
285
|
+
if (DEBUG_TTY_INPUT) {
|
|
286
|
+
try {
|
|
287
|
+
log(`TTY input ${formatTtyInputChunk(chunk)}`);
|
|
288
|
+
} catch (err) {
|
|
289
|
+
log(`TTY input log error: ${err.message}`);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
if (claudeProc) claudeProc.write(chunk);
|
|
293
|
+
};
|
|
281
294
|
ttyResizeHandler = () => {
|
|
282
295
|
if (claudeProc) claudeProc.resize(process.stdout.columns, process.stdout.rows);
|
|
283
296
|
};
|
|
@@ -644,10 +657,6 @@ function createTempImageFile(buffer, mediaType, uploadId) {
|
|
|
644
657
|
return tmpFile;
|
|
645
658
|
}
|
|
646
659
|
|
|
647
|
-
function shellQuote(value) {
|
|
648
|
-
return `'${String(value || '').replace(/'/g, `'\\''`)}'`;
|
|
649
|
-
}
|
|
650
|
-
|
|
651
660
|
function getLinuxClipboardTool() {
|
|
652
661
|
if (process.platform === 'win32' || process.platform === 'darwin') return null;
|
|
653
662
|
try {
|
|
@@ -674,6 +683,38 @@ function assertLinuxClipboardAvailable() {
|
|
|
674
683
|
if (tool) return tool;
|
|
675
684
|
throw new Error('Linux image paste requires xclip or wl-copy on the server. Install one and try again.');
|
|
676
685
|
}
|
|
686
|
+
|
|
687
|
+
function startLinuxClipboardImage(tmpFile, mediaType) {
|
|
688
|
+
const type = String(mediaType || 'image/png').toLowerCase();
|
|
689
|
+
const tool = assertLinuxClipboardAvailable();
|
|
690
|
+
const imageBuffer = fs.readFileSync(tmpFile);
|
|
691
|
+
const args = tool === 'xclip'
|
|
692
|
+
? ['-selection', 'clipboard', '-t', type, '-i', '-loops', '1']
|
|
693
|
+
: ['--type', type, '--paste-once'];
|
|
694
|
+
const child = spawn(tool, args, {
|
|
695
|
+
detached: true,
|
|
696
|
+
stdio: ['pipe', 'ignore', 'pipe'],
|
|
697
|
+
});
|
|
698
|
+
let stderr = '';
|
|
699
|
+
child.on('error', (err) => {
|
|
700
|
+
log(`Linux clipboard process error (${tool}): ${err.message}`);
|
|
701
|
+
});
|
|
702
|
+
child.stderr.on('data', (chunk) => {
|
|
703
|
+
stderr += chunk.toString('utf8');
|
|
704
|
+
if (stderr.length > 2000) stderr = stderr.slice(-2000);
|
|
705
|
+
});
|
|
706
|
+
child.on('exit', (code, signal) => {
|
|
707
|
+
const extra = stderr.trim() ? ` stderr=${JSON.stringify(stderr.trim())}` : '';
|
|
708
|
+
log(`Linux clipboard process exited (${tool}) code=${code ?? 'null'} signal=${signal ?? 'null'}${extra}`);
|
|
709
|
+
});
|
|
710
|
+
child.stdin.on('error', (err) => {
|
|
711
|
+
if (err.code !== 'EPIPE') log(`Linux clipboard stdin error (${tool}): ${err.message}`);
|
|
712
|
+
});
|
|
713
|
+
child.stdin.end(imageBuffer);
|
|
714
|
+
child.unref();
|
|
715
|
+
log(`Linux clipboard process started (${tool}) pid=${child.pid ?? 'null'} type=${type} bytes=${imageBuffer.length}`);
|
|
716
|
+
return tool;
|
|
717
|
+
}
|
|
677
718
|
|
|
678
719
|
setInterval(() => {
|
|
679
720
|
const now = Date.now();
|
|
@@ -1676,33 +1717,23 @@ function handlePreparedImageUpload({ tmpFile, mediaType, text, logLabel = '', on
|
|
|
1676
1717
|
} else if (isMac) {
|
|
1677
1718
|
execSync(`osascript -e 'set the clipboard to (read POSIX file "${tmpFile}" as 芦class PNGf禄)'`, { timeout: 10000 });
|
|
1678
1719
|
} else {
|
|
1679
|
-
const
|
|
1680
|
-
|
|
1681
|
-
const quotedPath = shellQuote(tmpFile);
|
|
1682
|
-
const quotedType = shellQuote(type);
|
|
1683
|
-
if (tool === 'xclip') {
|
|
1684
|
-
execSync(`xclip -selection clipboard -t ${quotedType} -i < ${quotedPath}`, {
|
|
1685
|
-
timeout: 10000,
|
|
1686
|
-
shell: '/bin/sh',
|
|
1687
|
-
});
|
|
1688
|
-
} else {
|
|
1689
|
-
execSync(`wl-copy --type ${quotedType} < ${quotedPath}`, {
|
|
1690
|
-
timeout: 10000,
|
|
1691
|
-
shell: '/bin/sh',
|
|
1692
|
-
});
|
|
1693
|
-
}
|
|
1694
|
-
log(`Linux clipboard set with ${tool} (${type})`);
|
|
1720
|
+
const tool = startLinuxClipboardImage(tmpFile, mediaType);
|
|
1721
|
+
log(`Linux clipboard armed with ${tool}`);
|
|
1695
1722
|
}
|
|
1696
1723
|
log('Clipboard set with image');
|
|
1697
1724
|
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1725
|
+
const pasteDelayMs = isWin || isMac ? 0 : 150;
|
|
1726
|
+
setTimeout(() => {
|
|
1727
|
+
if (!claudeProc) return;
|
|
1728
|
+
if (isWin) claudeProc.write('\x1bv');
|
|
1729
|
+
else claudeProc.write('\x16');
|
|
1730
|
+
log('Sent image paste keypress to PTY');
|
|
1731
|
+
}, pasteDelayMs);
|
|
1732
|
+
|
|
1733
|
+
setTimeout(() => {
|
|
1734
|
+
if (!claudeProc) return;
|
|
1735
|
+
const trimmedText = (text || '').trim();
|
|
1736
|
+
if (trimmedText) claudeProc.write(trimmedText);
|
|
1706
1737
|
|
|
1707
1738
|
setTimeout(() => {
|
|
1708
1739
|
if (claudeProc) claudeProc.write('\r');
|
|
@@ -1714,8 +1745,8 @@ function handlePreparedImageUpload({ tmpFile, mediaType, text, logLabel = '', on
|
|
|
1714
1745
|
try { fs.unlinkSync(tmpFile); } catch {}
|
|
1715
1746
|
}
|
|
1716
1747
|
}, 5000);
|
|
1717
|
-
}, 150);
|
|
1718
|
-
}, 1000);
|
|
1748
|
+
}, 150);
|
|
1749
|
+
}, 1000 + pasteDelayMs);
|
|
1719
1750
|
} catch (err) {
|
|
1720
1751
|
log(`Image upload error: ${err.message}`);
|
|
1721
1752
|
if (onCleanup) onCleanup();
|