claude-remote-cli 1.9.0 → 1.9.1

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-remote-cli",
3
- "version": "1.9.0",
3
+ "version": "1.9.1",
4
4
  "description": "Remote web interface for Claude Code CLI sessions",
5
5
  "type": "module",
6
6
  "main": "dist/server/index.js",
package/public/app.js CHANGED
@@ -162,65 +162,59 @@
162
162
  term.onScroll(updateScrollbar);
163
163
  term.onWriteParsed(updateScrollbar);
164
164
 
165
- term.onData(function (data) {
166
- if (ws && ws.readyState === WebSocket.OPEN) {
167
- ws.send(data);
165
+ var isMac = /Mac|iPhone|iPad|iPod/.test(navigator.platform || '');
166
+ term.attachCustomKeyEventHandler(function (e) {
167
+ if (isMobileDevice) {
168
+ return false;
168
169
  }
169
- });
170
170
 
171
- // On Windows/Linux, Ctrl+V is the paste shortcut but xterm.js intercepts it
172
- // internally without firing a native paste event, so our image paste handler
173
- // on terminalContainer never runs. Intercept Ctrl+V here to check for images.
174
- // On macOS, Ctrl+V sends a raw \x16 to the terminal (used by vim etc.), so
175
- // we only intercept on non-Mac platforms.
176
- var isMac = /Mac|iPhone|iPad|iPod/.test(navigator.platform || '');
177
- if (!isMac) {
178
- term.attachCustomKeyEventHandler(function (e) {
179
- if (e.type === 'keydown' && e.ctrlKey && !e.shiftKey && !e.altKey && !e.metaKey &&
180
- (e.key === 'v' || e.key === 'V')) {
181
- if (navigator.clipboard && navigator.clipboard.read) {
182
- navigator.clipboard.read().then(function (clipboardItems) {
183
- var imageBlob = null;
184
- var imageType = null;
185
-
186
- for (var i = 0; i < clipboardItems.length; i++) {
187
- var types = clipboardItems[i].types;
188
- for (var j = 0; j < types.length; j++) {
189
- if (types[j].indexOf('image/') === 0) {
190
- imageType = types[j];
191
- imageBlob = clipboardItems[i];
192
- break;
193
- }
171
+ if (!isMac && e.type === 'keydown' && e.ctrlKey && !e.shiftKey && !e.altKey && !e.metaKey &&
172
+ (e.key === 'v' || e.key === 'V')) {
173
+ if (navigator.clipboard && navigator.clipboard.read) {
174
+ navigator.clipboard.read().then(function (clipboardItems) {
175
+ var imageBlob = null;
176
+ var imageType = null;
177
+
178
+ for (var i = 0; i < clipboardItems.length; i++) {
179
+ var types = clipboardItems[i].types;
180
+ for (var j = 0; j < types.length; j++) {
181
+ if (types[j].indexOf('image/') === 0) {
182
+ imageType = types[j];
183
+ imageBlob = clipboardItems[i];
184
+ break;
194
185
  }
195
- if (imageBlob) break;
196
186
  }
187
+ if (imageBlob) break;
188
+ }
197
189
 
198
- if (imageBlob) {
199
- imageBlob.getType(imageType).then(function (blob) {
200
- uploadImage(blob, imageType);
201
- });
202
- } else {
203
- navigator.clipboard.readText().then(function (text) {
204
- if (text) term.paste(text);
205
- });
206
- }
207
- }).catch(function () {
208
- // Clipboard read failed (permission denied, etc.) — fall back to text.
209
- // If readText also fails, paste is lost for this keypress; this only
210
- // happens when clipboard permission is fully denied, which is rare
211
- // for user-gesture-triggered reads on HTTPS origins.
212
- if (navigator.clipboard.readText) {
213
- navigator.clipboard.readText().then(function (text) {
214
- if (text) term.paste(text);
215
- }).catch(function () {});
216
- }
217
- });
218
- return false; // Prevent xterm from handling Ctrl+V
219
- }
190
+ if (imageBlob) {
191
+ imageBlob.getType(imageType).then(function (blob) {
192
+ uploadImage(blob, imageType);
193
+ });
194
+ } else {
195
+ navigator.clipboard.readText().then(function (text) {
196
+ if (text) term.paste(text);
197
+ });
198
+ }
199
+ }).catch(function () {
200
+ if (navigator.clipboard.readText) {
201
+ navigator.clipboard.readText().then(function (text) {
202
+ if (text) term.paste(text);
203
+ }).catch(function () {});
204
+ }
205
+ });
206
+ return false;
220
207
  }
221
- return true; // Let xterm handle all other keys
222
- });
223
- }
208
+ }
209
+
210
+ return true;
211
+ });
212
+
213
+ term.onData(function (data) {
214
+ if (ws && ws.readyState === WebSocket.OPEN) {
215
+ ws.send(data);
216
+ }
217
+ });
224
218
 
225
219
  var resizeObserver = new ResizeObserver(function () {
226
220
  fitAddon.fit();
@@ -331,7 +325,13 @@
331
325
  delete attentionSessions[sessionId];
332
326
  noSessionMsg.hidden = true;
333
327
  term.clear();
334
- term.focus();
328
+ if (isMobileDevice) {
329
+ mobileInput.value = '';
330
+ mobileInput.dispatchEvent(new Event('sessionchange'));
331
+ mobileInput.focus();
332
+ } else {
333
+ term.focus();
334
+ }
335
335
  closeSidebar();
336
336
  updateSessionTitle();
337
337
  highlightActiveSession();
@@ -1277,6 +1277,7 @@
1277
1277
  if (!isMobileDevice) return;
1278
1278
 
1279
1279
  var lastInputValue = '';
1280
+ var isComposing = false;
1280
1281
 
1281
1282
  function focusMobileInput() {
1282
1283
  if (document.activeElement !== mobileInput) {
@@ -1336,6 +1337,31 @@
1336
1337
  }
1337
1338
  }
1338
1339
 
1340
+ mobileInput.addEventListener('compositionstart', function () {
1341
+ isComposing = true;
1342
+ });
1343
+
1344
+ mobileInput.addEventListener('compositionend', function () {
1345
+ if (ws && ws.readyState === WebSocket.OPEN) {
1346
+ var currentValue = mobileInput.value;
1347
+ sendInputDiff(currentValue);
1348
+ lastInputValue = currentValue;
1349
+ }
1350
+ setTimeout(function () { isComposing = false; }, 0);
1351
+ });
1352
+
1353
+ mobileInput.addEventListener('blur', function () {
1354
+ if (isComposing) {
1355
+ isComposing = false;
1356
+ lastInputValue = mobileInput.value;
1357
+ }
1358
+ });
1359
+
1360
+ mobileInput.addEventListener('sessionchange', function () {
1361
+ isComposing = false;
1362
+ lastInputValue = '';
1363
+ });
1364
+
1339
1365
  // Handle text input with autocorrect
1340
1366
  var clearTimer = null;
1341
1367
  mobileInput.addEventListener('input', function () {
@@ -1348,6 +1374,8 @@
1348
1374
 
1349
1375
  if (!ws || ws.readyState !== WebSocket.OPEN) return;
1350
1376
 
1377
+ if (isComposing) return;
1378
+
1351
1379
  var currentValue = mobileInput.value;
1352
1380
  sendInputDiff(currentValue);
1353
1381
  lastInputValue = currentValue;
@@ -1379,24 +1407,14 @@
1379
1407
  lastInputValue = '';
1380
1408
  break;
1381
1409
  case 'Tab':
1382
- e.preventDefault();
1383
1410
  ws.send('\t');
1384
1411
  break;
1385
1412
  case 'ArrowUp':
1386
- e.preventDefault();
1387
1413
  ws.send('\x1b[A');
1388
1414
  break;
1389
1415
  case 'ArrowDown':
1390
- e.preventDefault();
1391
1416
  ws.send('\x1b[B');
1392
1417
  break;
1393
- case 'ArrowLeft':
1394
- // Let input handle cursor movement for autocorrect
1395
- handled = false;
1396
- break;
1397
- case 'ArrowRight':
1398
- handled = false;
1399
- break;
1400
1418
  default:
1401
1419
  handled = false;
1402
1420
  }
@@ -0,0 +1,6 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
2
+ <rect width="512" height="512" rx="64" fill="#1a1a1a"/>
3
+ <text x="256" y="200" text-anchor="middle" font-family="monospace" font-size="72" font-weight="bold" fill="#d97757">&gt;_</text>
4
+ <text x="256" y="320" text-anchor="middle" font-family="-apple-system,sans-serif" font-size="56" font-weight="600" fill="#ececec">Claude</text>
5
+ <text x="256" y="390" text-anchor="middle" font-family="-apple-system,sans-serif" font-size="40" fill="#9b9b9b">Remote CLI</text>
6
+ </svg>
package/public/index.html CHANGED
@@ -5,6 +5,8 @@
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover" />
6
6
  <title>Claude Remote CLI</title>
7
7
  <link rel="manifest" href="/manifest.json" />
8
+ <link rel="icon" href="/icon.svg" type="image/svg+xml" />
9
+ <link rel="apple-touch-icon" href="/icon.svg" />
8
10
  <meta name="mobile-web-app-capable" content="yes" />
9
11
  <meta name="apple-mobile-web-app-capable" content="yes" />
10
12
  <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
@@ -64,7 +66,7 @@
64
66
  <button id="menu-btn" class="icon-btn" aria-label="Open sessions menu">&#9776;</button>
65
67
  <span id="session-title" class="mobile-title">No session</span>
66
68
  </div>
67
- <input type="text" id="mobile-input" autocomplete="on" autocorrect="on" autocapitalize="sentences" spellcheck="true" aria-label="Terminal input" />
69
+ <input type="text" id="mobile-input" dir="ltr" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" aria-label="Terminal input" />
68
70
  <div id="terminal-container"></div>
69
71
  <div id="terminal-scrollbar"><div id="terminal-scrollbar-thumb"></div></div>
70
72
  <div id="no-session-msg">No active session. Create or select a session to begin.</div>
@@ -4,5 +4,13 @@
4
4
  "display": "standalone",
5
5
  "start_url": "/",
6
6
  "background_color": "#1a1a1a",
7
- "theme_color": "#1a1a1a"
7
+ "theme_color": "#1a1a1a",
8
+ "icons": [
9
+ {
10
+ "src": "/icon.svg",
11
+ "sizes": "any",
12
+ "type": "image/svg+xml",
13
+ "purpose": "any"
14
+ }
15
+ ]
8
16
  }
package/public/style.css CHANGED
@@ -470,13 +470,20 @@ html, body {
470
470
 
471
471
  #mobile-input {
472
472
  position: absolute;
473
- top: -9999px;
474
- left: -9999px;
473
+ left: 0;
474
+ top: 0;
475
475
  width: 1px;
476
476
  height: 1px;
477
477
  opacity: 0;
478
478
  font-size: 16px; /* prevents iOS zoom on focus */
479
479
  z-index: -1;
480
+ border: 0;
481
+ padding: 0;
482
+ margin: 0;
483
+ outline: none;
484
+ color: transparent;
485
+ caret-color: transparent;
486
+ background: transparent;
480
487
  }
481
488
 
482
489
  /* ===== Terminal Scrollbar ===== */