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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/server.js +76 -45
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-remote",
3
- "version": "0.4.1",
3
+ "version": "0.4.3",
4
4
  "description": "Remote control bridge for Claude Code REPL - drive from phone/WebUI",
5
5
  "main": "server.js",
6
6
  "bin": {
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 (claudeProc) claudeProc.write(chunk);
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 type = String(mediaType || 'image/png').toLowerCase();
1680
- const tool = assertLinuxClipboardAvailable();
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
- if (isWin) claudeProc.write('\x1bv');
1699
- else claudeProc.write('\x16');
1700
- log('Sent image paste keypress to PTY');
1701
-
1702
- setTimeout(() => {
1703
- if (!claudeProc) return;
1704
- const trimmedText = (text || '').trim();
1705
- if (trimmedText) claudeProc.write(trimmedText);
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();