nikcli-remote 1.0.2 → 1.0.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.
@@ -1665,6 +1665,7 @@ function getWebClient() {
1665
1665
  <meta name="apple-mobile-web-app-capable" content="yes">
1666
1666
  <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
1667
1667
  <title>NikCLI Remote</title>
1668
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/xterm@5.3.0/css/xterm.css" />
1668
1669
  <style>
1669
1670
  * { box-sizing: border-box; margin: 0; padding: 0; }
1670
1671
  :root {
@@ -1682,7 +1683,7 @@ function getWebClient() {
1682
1683
  display: flex;
1683
1684
  flex-direction: column;
1684
1685
  }
1685
- #terminal { flex: 1; overflow: hidden; background: var(--bg-primary); }
1686
+ #terminal { flex: 1; overflow: hidden; background: var(--bg-primary); padding: 8px; }
1686
1687
  #input-area {
1687
1688
  background: var(--bg-secondary);
1688
1689
  border-top: 1px solid var(--border);
@@ -1797,10 +1798,10 @@ function getWebClient() {
1797
1798
  </form>
1798
1799
  </div>
1799
1800
 
1800
- <!-- hterm from Google's Chromium -->
1801
- <script src="https://cdn.jsdelivr.net/npm/hterm@1.1.1/lib/hterm.js"></script>
1801
+ <script src="https://cdn.jsdelivr.net/npm/xterm@5.3.0/lib/xterm.js"></script>
1802
+ <script src="https://cdn.jsdelivr.net/npm/xterm-addon-fit@0.8.0/lib/xterm-addon-fit.js"></script>
1802
1803
  <script>
1803
- let ws = null, term = null, connected = false, reconnectAttempts = 0;
1804
+ let ws = null, term = null, fitAddon = null, connected = false, reconnectAttempts = 0;
1804
1805
  const token = new URLSearchParams(location.search).get('t') || '';
1805
1806
  const sessionId = new URLSearchParams(location.search).get('s') || '';
1806
1807
 
@@ -1812,40 +1813,57 @@ function getWebClient() {
1812
1813
  const sessionSpan = document.getElementById('session-id');
1813
1814
  const cmdInput = document.getElementById('cmd-input');
1814
1815
 
1815
- // Initialize hterm
1816
- hterm.DefaultCharWidth = 8.53;
1817
- hterm.DefaultRowHeight = 17;
1818
-
1819
- const storage = new hterm.Storage.Memory();
1820
- const prefs = new hterm.Preferences(storage);
1821
- prefs.set('font-size', 14);
1822
- prefs.set('font-family', '"SF Mono", Monaco, Consolas, monospace');
1823
- prefs.set('background-color', '#0d1117');
1824
- prefs.set('foreground-color', '#e6edf3');
1825
- prefs.set('cursor-color', '#3fb950');
1826
- prefs.set('selection-color', '#264f78');
1827
- prefs.set('scrollbar-visible', false);
1828
- prefs.set('cursor-blink', true);
1816
+ // Initialize xterm.js
1817
+ term = new Terminal({
1818
+ cursorBlink: true,
1819
+ fontSize: 14,
1820
+ fontFamily: '"SF Mono", Monaco, Consolas, monospace',
1821
+ theme: {
1822
+ background: '#0d1117',
1823
+ foreground: '#e6edf3',
1824
+ cursor: '#3fb950',
1825
+ selectionBackground: '#264f78',
1826
+ black: '#484f58',
1827
+ red: '#f85149',
1828
+ green: '#3fb950',
1829
+ yellow: '#d29922',
1830
+ blue: '#58a6ff',
1831
+ magenta: '#a371f7',
1832
+ cyan: '#39c5cf',
1833
+ white: '#e6edf3',
1834
+ brightBlack: '#6e7681',
1835
+ brightRed: '#ffa198',
1836
+ brightGreen: '#7ee787',
1837
+ brightYellow: '#f0883e',
1838
+ brightBlue: '#79c0ff',
1839
+ brightMagenta: '#d2a8ff',
1840
+ brightCyan: '#56d4db',
1841
+ brightWhite: '#f0f6fc'
1842
+ },
1843
+ convertEol: true
1844
+ });
1829
1845
 
1830
- term = new hterm.Terminal(storage, prefs);
1831
- term.onTerminalReady = function() {
1832
- term.io.writeUTF8('\x1B[32mInitializing NikCLI Remote...\x1B[0m\r
1833
- ');
1834
- term.io.writeUTF8('\x1B[90mType commands below\x1B[0m\r
1835
- ');
1836
- connect();
1837
- };
1838
- term.decorate(document.getElementById('terminal'));
1839
- term.start();
1846
+ fitAddon = new FitAddon.FitAddon();
1847
+ term.loadAddon(fitAddon);
1848
+ term.open(document.getElementById('terminal'));
1849
+ fitAddon.fit();
1850
+ term.writeln('\x1B[32mInitializing NikCLI Remote...\x1B[0m');
1851
+ term.writeln('\x1B[90mType commands below\x1B[0m');
1840
1852
 
1841
1853
  // Resize handler
1842
1854
  window.addEventListener('resize', () => {
1843
- const el = document.getElementById('terminal');
1844
- if (term && el) {
1845
- term.setHeightAndWidth(el.clientHeight / 17, el.clientWidth / 8.53);
1846
- }
1855
+ clearTimeout(window.resizeTimer);
1856
+ window.resizeTimer = setTimeout(() => fitAddon.fit(), 100);
1847
1857
  });
1848
1858
 
1859
+ // Visual viewport for mobile keyboard
1860
+ if (window.visualViewport) {
1861
+ window.visualViewport.addEventListener('resize', () => {
1862
+ clearTimeout(window.resizeTimer);
1863
+ window.resizeTimer = setTimeout(() => fitAddon.fit(), 100);
1864
+ });
1865
+ }
1866
+
1849
1867
  function connect() {
1850
1868
  const protocol = location.protocol === 'https:' ? 'wss:' : 'ws:';
1851
1869
  ws = new WebSocket(protocol + '//' + location.host + '/ws');
@@ -1880,8 +1898,8 @@ function getWebClient() {
1880
1898
  authOverlay.classList.add('hidden');
1881
1899
  setStatus('connected', 'Connected');
1882
1900
  sessionSpan.textContent = sessionId ? 'Session: ' + sessionId : '';
1883
- term.io.writeUTF8('\r
1884
- \x1B[32m\u2713 Connected to NikCLI\x1B[0m\r
1901
+ term.writeln('
1902
+ \x1B[32m\u2713 Connected to NikCLI\x1B[0m
1885
1903
  ');
1886
1904
  break;
1887
1905
  case 'auth:failed':
@@ -1889,11 +1907,11 @@ function getWebClient() {
1889
1907
  authText.textContent = 'Authentication failed';
1890
1908
  break;
1891
1909
  case 'terminal:output':
1892
- if (msg.payload?.data) term.io.writeUTF8(msg.payload.data);
1910
+ if (msg.payload?.data) term.write(msg.payload.data);
1893
1911
  break;
1894
1912
  case 'terminal:exit':
1895
- term.io.writeUTF8('\r
1896
- \x1B[33m[Process exited: ' + (msg.payload?.code || 0) + ']\x1B[0m\r
1913
+ term.writeln('
1914
+ \x1B[33m[Process exited: ' + (msg.payload?.code || 0) + ']\x1B[0m
1897
1915
  ');
1898
1916
  break;
1899
1917
  }
@@ -1909,7 +1927,7 @@ function getWebClient() {
1909
1927
  const value = cmdInput.value.trim();
1910
1928
  if (!value || !connected) return;
1911
1929
  cmdInput.value = '';
1912
- term.io.writeUTF8('$ ' + value + '\r
1930
+ term.write('$ ' + value + '\r
1913
1931
  ');
1914
1932
  ws.send(JSON.stringify({ type: 'terminal:input', data: value + '\r' }));
1915
1933
  setTimeout(() => cmdInput.focus(), 50);
@@ -1918,7 +1936,7 @@ function getWebClient() {
1918
1936
 
1919
1937
  function send(cmd) {
1920
1938
  if (!connected) return;
1921
- term.io.writeUTF8('$ ' + cmd + '\r
1939
+ term.write('$ ' + cmd + '\r
1922
1940
  ');
1923
1941
  ws.send(JSON.stringify({ type: 'terminal:input', data: cmd + '\r' }));
1924
1942
  }
@@ -1933,6 +1951,8 @@ function getWebClient() {
1933
1951
  document.getElementById('terminal')?.addEventListener('click', () => {
1934
1952
  if (connected) cmdInput.focus();
1935
1953
  });
1954
+
1955
+ connect();
1936
1956
  </script>
1937
1957
  </body>
1938
1958
  </html>`;
package/dist/index.cjs CHANGED
@@ -5507,6 +5507,7 @@ function getWebClient() {
5507
5507
  <meta name="apple-mobile-web-app-capable" content="yes">
5508
5508
  <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
5509
5509
  <title>NikCLI Remote</title>
5510
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/xterm@5.3.0/css/xterm.css" />
5510
5511
  <style>
5511
5512
  * { box-sizing: border-box; margin: 0; padding: 0; }
5512
5513
  :root {
@@ -5524,7 +5525,7 @@ function getWebClient() {
5524
5525
  display: flex;
5525
5526
  flex-direction: column;
5526
5527
  }
5527
- #terminal { flex: 1; overflow: hidden; background: var(--bg-primary); }
5528
+ #terminal { flex: 1; overflow: hidden; background: var(--bg-primary); padding: 8px; }
5528
5529
  #input-area {
5529
5530
  background: var(--bg-secondary);
5530
5531
  border-top: 1px solid var(--border);
@@ -5639,10 +5640,10 @@ function getWebClient() {
5639
5640
  </form>
5640
5641
  </div>
5641
5642
 
5642
- <!-- hterm from Google's Chromium -->
5643
- <script src="https://cdn.jsdelivr.net/npm/hterm@1.1.1/lib/hterm.js"></script>
5643
+ <script src="https://cdn.jsdelivr.net/npm/xterm@5.3.0/lib/xterm.js"></script>
5644
+ <script src="https://cdn.jsdelivr.net/npm/xterm-addon-fit@0.8.0/lib/xterm-addon-fit.js"></script>
5644
5645
  <script>
5645
- let ws = null, term = null, connected = false, reconnectAttempts = 0;
5646
+ let ws = null, term = null, fitAddon = null, connected = false, reconnectAttempts = 0;
5646
5647
  const token = new URLSearchParams(location.search).get('t') || '';
5647
5648
  const sessionId = new URLSearchParams(location.search).get('s') || '';
5648
5649
 
@@ -5654,40 +5655,57 @@ function getWebClient() {
5654
5655
  const sessionSpan = document.getElementById('session-id');
5655
5656
  const cmdInput = document.getElementById('cmd-input');
5656
5657
 
5657
- // Initialize hterm
5658
- hterm.DefaultCharWidth = 8.53;
5659
- hterm.DefaultRowHeight = 17;
5660
-
5661
- const storage = new hterm.Storage.Memory();
5662
- const prefs = new hterm.Preferences(storage);
5663
- prefs.set('font-size', 14);
5664
- prefs.set('font-family', '"SF Mono", Monaco, Consolas, monospace');
5665
- prefs.set('background-color', '#0d1117');
5666
- prefs.set('foreground-color', '#e6edf3');
5667
- prefs.set('cursor-color', '#3fb950');
5668
- prefs.set('selection-color', '#264f78');
5669
- prefs.set('scrollbar-visible', false);
5670
- prefs.set('cursor-blink', true);
5658
+ // Initialize xterm.js
5659
+ term = new Terminal({
5660
+ cursorBlink: true,
5661
+ fontSize: 14,
5662
+ fontFamily: '"SF Mono", Monaco, Consolas, monospace',
5663
+ theme: {
5664
+ background: '#0d1117',
5665
+ foreground: '#e6edf3',
5666
+ cursor: '#3fb950',
5667
+ selectionBackground: '#264f78',
5668
+ black: '#484f58',
5669
+ red: '#f85149',
5670
+ green: '#3fb950',
5671
+ yellow: '#d29922',
5672
+ blue: '#58a6ff',
5673
+ magenta: '#a371f7',
5674
+ cyan: '#39c5cf',
5675
+ white: '#e6edf3',
5676
+ brightBlack: '#6e7681',
5677
+ brightRed: '#ffa198',
5678
+ brightGreen: '#7ee787',
5679
+ brightYellow: '#f0883e',
5680
+ brightBlue: '#79c0ff',
5681
+ brightMagenta: '#d2a8ff',
5682
+ brightCyan: '#56d4db',
5683
+ brightWhite: '#f0f6fc'
5684
+ },
5685
+ convertEol: true
5686
+ });
5671
5687
 
5672
- term = new hterm.Terminal(storage, prefs);
5673
- term.onTerminalReady = function() {
5674
- term.io.writeUTF8('\x1B[32mInitializing NikCLI Remote...\x1B[0m\r
5675
- ');
5676
- term.io.writeUTF8('\x1B[90mType commands below\x1B[0m\r
5677
- ');
5678
- connect();
5679
- };
5680
- term.decorate(document.getElementById('terminal'));
5681
- term.start();
5688
+ fitAddon = new FitAddon.FitAddon();
5689
+ term.loadAddon(fitAddon);
5690
+ term.open(document.getElementById('terminal'));
5691
+ fitAddon.fit();
5692
+ term.writeln('\x1B[32mInitializing NikCLI Remote...\x1B[0m');
5693
+ term.writeln('\x1B[90mType commands below\x1B[0m');
5682
5694
 
5683
5695
  // Resize handler
5684
5696
  window.addEventListener('resize', () => {
5685
- const el = document.getElementById('terminal');
5686
- if (term && el) {
5687
- term.setHeightAndWidth(el.clientHeight / 17, el.clientWidth / 8.53);
5688
- }
5697
+ clearTimeout(window.resizeTimer);
5698
+ window.resizeTimer = setTimeout(() => fitAddon.fit(), 100);
5689
5699
  });
5690
5700
 
5701
+ // Visual viewport for mobile keyboard
5702
+ if (window.visualViewport) {
5703
+ window.visualViewport.addEventListener('resize', () => {
5704
+ clearTimeout(window.resizeTimer);
5705
+ window.resizeTimer = setTimeout(() => fitAddon.fit(), 100);
5706
+ });
5707
+ }
5708
+
5691
5709
  function connect() {
5692
5710
  const protocol = location.protocol === 'https:' ? 'wss:' : 'ws:';
5693
5711
  ws = new WebSocket(protocol + '//' + location.host + '/ws');
@@ -5722,8 +5740,8 @@ function getWebClient() {
5722
5740
  authOverlay.classList.add('hidden');
5723
5741
  setStatus('connected', 'Connected');
5724
5742
  sessionSpan.textContent = sessionId ? 'Session: ' + sessionId : '';
5725
- term.io.writeUTF8('\r
5726
- \x1B[32m\u2713 Connected to NikCLI\x1B[0m\r
5743
+ term.writeln('
5744
+ \x1B[32m\u2713 Connected to NikCLI\x1B[0m
5727
5745
  ');
5728
5746
  break;
5729
5747
  case 'auth:failed':
@@ -5731,11 +5749,11 @@ function getWebClient() {
5731
5749
  authText.textContent = 'Authentication failed';
5732
5750
  break;
5733
5751
  case 'terminal:output':
5734
- if (msg.payload?.data) term.io.writeUTF8(msg.payload.data);
5752
+ if (msg.payload?.data) term.write(msg.payload.data);
5735
5753
  break;
5736
5754
  case 'terminal:exit':
5737
- term.io.writeUTF8('\r
5738
- \x1B[33m[Process exited: ' + (msg.payload?.code || 0) + ']\x1B[0m\r
5755
+ term.writeln('
5756
+ \x1B[33m[Process exited: ' + (msg.payload?.code || 0) + ']\x1B[0m
5739
5757
  ');
5740
5758
  break;
5741
5759
  }
@@ -5751,7 +5769,7 @@ function getWebClient() {
5751
5769
  const value = cmdInput.value.trim();
5752
5770
  if (!value || !connected) return;
5753
5771
  cmdInput.value = '';
5754
- term.io.writeUTF8('$ ' + value + '\r
5772
+ term.write('$ ' + value + '\r
5755
5773
  ');
5756
5774
  ws.send(JSON.stringify({ type: 'terminal:input', data: value + '\r' }));
5757
5775
  setTimeout(() => cmdInput.focus(), 50);
@@ -5760,7 +5778,7 @@ function getWebClient() {
5760
5778
 
5761
5779
  function send(cmd) {
5762
5780
  if (!connected) return;
5763
- term.io.writeUTF8('$ ' + cmd + '\r
5781
+ term.write('$ ' + cmd + '\r
5764
5782
  ');
5765
5783
  ws.send(JSON.stringify({ type: 'terminal:input', data: cmd + '\r' }));
5766
5784
  }
@@ -5775,6 +5793,8 @@ function getWebClient() {
5775
5793
  document.getElementById('terminal')?.addEventListener('click', () => {
5776
5794
  if (connected) cmdInput.focus();
5777
5795
  });
5796
+
5797
+ connect();
5778
5798
  </script>
5779
5799
  </body>
5780
5800
  </html>`;
package/dist/index.d.cts CHANGED
@@ -285,7 +285,7 @@ declare class TerminalManager extends EventEmitter {
285
285
 
286
286
  /**
287
287
  * @nikcli/remote - Ghostty-web Style Terminal Client
288
- * Uses hterm for full terminal emulation
288
+ * Uses xterm.js with full terminal emulation
289
289
  */
290
290
  declare function getWebClient(): string;
291
291
 
package/dist/index.d.ts CHANGED
@@ -285,7 +285,7 @@ declare class TerminalManager extends EventEmitter {
285
285
 
286
286
  /**
287
287
  * @nikcli/remote - Ghostty-web Style Terminal Client
288
- * Uses hterm for full terminal emulation
288
+ * Uses xterm.js with full terminal emulation
289
289
  */
290
290
  declare function getWebClient(): string;
291
291
 
package/dist/index.js CHANGED
@@ -4,12 +4,12 @@ import {
4
4
  RemoteServer,
5
5
  TerminalManager,
6
6
  getWebClient
7
- } from "./chunk-DRL7JX54.js";
7
+ } from "./chunk-ONRUR3Z7.js";
8
8
  import "./chunk-MCKGQKYU.js";
9
9
 
10
10
  // src/index.ts
11
11
  async function createRemoteServer(config = {}) {
12
- const { RemoteServer: RemoteServer2 } = await import("./server-J7SPDGZO.js");
12
+ const { RemoteServer: RemoteServer2 } = await import("./server-MURDBK6L.js");
13
13
  const server = new RemoteServer2(config);
14
14
  const session = await server.start();
15
15
  return { server, session };
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  RemoteServer
3
- } from "./chunk-DRL7JX54.js";
3
+ } from "./chunk-ONRUR3Z7.js";
4
4
  import "./chunk-MCKGQKYU.js";
5
5
  export {
6
6
  RemoteServer
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nikcli-remote",
3
- "version": "1.0.2",
3
+ "version": "1.0.4",
4
4
  "description": "Native remote terminal server for NikCLI - Mobile control via WebSocket",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
package/src/web-client.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * @nikcli/remote - Ghostty-web Style Terminal Client
3
- * Uses hterm for full terminal emulation
3
+ * Uses xterm.js with full terminal emulation
4
4
  */
5
5
 
6
6
  export function getWebClient(): string {
@@ -12,6 +12,7 @@ export function getWebClient(): string {
12
12
  <meta name="apple-mobile-web-app-capable" content="yes">
13
13
  <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
14
14
  <title>NikCLI Remote</title>
15
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/xterm@5.3.0/css/xterm.css" />
15
16
  <style>
16
17
  * { box-sizing: border-box; margin: 0; padding: 0; }
17
18
  :root {
@@ -29,7 +30,7 @@ export function getWebClient(): string {
29
30
  display: flex;
30
31
  flex-direction: column;
31
32
  }
32
- #terminal { flex: 1; overflow: hidden; background: var(--bg-primary); }
33
+ #terminal { flex: 1; overflow: hidden; background: var(--bg-primary); padding: 8px; }
33
34
  #input-area {
34
35
  background: var(--bg-secondary);
35
36
  border-top: 1px solid var(--border);
@@ -144,10 +145,10 @@ export function getWebClient(): string {
144
145
  </form>
145
146
  </div>
146
147
 
147
- <!-- hterm from Google's Chromium -->
148
- <script src="https://cdn.jsdelivr.net/npm/hterm@1.1.1/lib/hterm.js"></script>
148
+ <script src="https://cdn.jsdelivr.net/npm/xterm@5.3.0/lib/xterm.js"></script>
149
+ <script src="https://cdn.jsdelivr.net/npm/xterm-addon-fit@0.8.0/lib/xterm-addon-fit.js"></script>
149
150
  <script>
150
- let ws = null, term = null, connected = false, reconnectAttempts = 0;
151
+ let ws = null, term = null, fitAddon = null, connected = false, reconnectAttempts = 0;
151
152
  const token = new URLSearchParams(location.search).get('t') || '';
152
153
  const sessionId = new URLSearchParams(location.search).get('s') || '';
153
154
 
@@ -159,38 +160,57 @@ export function getWebClient(): string {
159
160
  const sessionSpan = document.getElementById('session-id');
160
161
  const cmdInput = document.getElementById('cmd-input');
161
162
 
162
- // Initialize hterm
163
- hterm.DefaultCharWidth = 8.53;
164
- hterm.DefaultRowHeight = 17;
165
-
166
- const storage = new hterm.Storage.Memory();
167
- const prefs = new hterm.Preferences(storage);
168
- prefs.set('font-size', 14);
169
- prefs.set('font-family', '"SF Mono", Monaco, Consolas, monospace');
170
- prefs.set('background-color', '#0d1117');
171
- prefs.set('foreground-color', '#e6edf3');
172
- prefs.set('cursor-color', '#3fb950');
173
- prefs.set('selection-color', '#264f78');
174
- prefs.set('scrollbar-visible', false);
175
- prefs.set('cursor-blink', true);
163
+ // Initialize xterm.js
164
+ term = new Terminal({
165
+ cursorBlink: true,
166
+ fontSize: 14,
167
+ fontFamily: '"SF Mono", Monaco, Consolas, monospace',
168
+ theme: {
169
+ background: '#0d1117',
170
+ foreground: '#e6edf3',
171
+ cursor: '#3fb950',
172
+ selectionBackground: '#264f78',
173
+ black: '#484f58',
174
+ red: '#f85149',
175
+ green: '#3fb950',
176
+ yellow: '#d29922',
177
+ blue: '#58a6ff',
178
+ magenta: '#a371f7',
179
+ cyan: '#39c5cf',
180
+ white: '#e6edf3',
181
+ brightBlack: '#6e7681',
182
+ brightRed: '#ffa198',
183
+ brightGreen: '#7ee787',
184
+ brightYellow: '#f0883e',
185
+ brightBlue: '#79c0ff',
186
+ brightMagenta: '#d2a8ff',
187
+ brightCyan: '#56d4db',
188
+ brightWhite: '#f0f6fc'
189
+ },
190
+ convertEol: true
191
+ });
176
192
 
177
- term = new hterm.Terminal(storage, prefs);
178
- term.onTerminalReady = function() {
179
- term.io.writeUTF8('\x1b[32mInitializing NikCLI Remote...\x1b[0m\r\n');
180
- term.io.writeUTF8('\x1b[90mType commands below\x1b[0m\r\n');
181
- connect();
182
- };
183
- term.decorate(document.getElementById('terminal'));
184
- term.start();
193
+ fitAddon = new FitAddon.FitAddon();
194
+ term.loadAddon(fitAddon);
195
+ term.open(document.getElementById('terminal'));
196
+ fitAddon.fit();
197
+ term.writeln('\x1b[32mInitializing NikCLI Remote...\x1b[0m');
198
+ term.writeln('\x1b[90mType commands below\x1b[0m');
185
199
 
186
200
  // Resize handler
187
201
  window.addEventListener('resize', () => {
188
- const el = document.getElementById('terminal');
189
- if (term && el) {
190
- term.setHeightAndWidth(el.clientHeight / 17, el.clientWidth / 8.53);
191
- }
202
+ clearTimeout(window.resizeTimer);
203
+ window.resizeTimer = setTimeout(() => fitAddon.fit(), 100);
192
204
  });
193
205
 
206
+ // Visual viewport for mobile keyboard
207
+ if (window.visualViewport) {
208
+ window.visualViewport.addEventListener('resize', () => {
209
+ clearTimeout(window.resizeTimer);
210
+ window.resizeTimer = setTimeout(() => fitAddon.fit(), 100);
211
+ });
212
+ }
213
+
194
214
  function connect() {
195
215
  const protocol = location.protocol === 'https:' ? 'wss:' : 'ws:';
196
216
  ws = new WebSocket(protocol + '//' + location.host + '/ws');
@@ -225,17 +245,17 @@ export function getWebClient(): string {
225
245
  authOverlay.classList.add('hidden');
226
246
  setStatus('connected', 'Connected');
227
247
  sessionSpan.textContent = sessionId ? 'Session: ' + sessionId : '';
228
- term.io.writeUTF8('\r\n\x1b[32m✓ Connected to NikCLI\x1b[0m\r\n');
248
+ term.writeln('\n\x1b[32m✓ Connected to NikCLI\x1b[0m\n');
229
249
  break;
230
250
  case 'auth:failed':
231
251
  authMsg.classList.add('error');
232
252
  authText.textContent = 'Authentication failed';
233
253
  break;
234
254
  case 'terminal:output':
235
- if (msg.payload?.data) term.io.writeUTF8(msg.payload.data);
255
+ if (msg.payload?.data) term.write(msg.payload.data);
236
256
  break;
237
257
  case 'terminal:exit':
238
- term.io.writeUTF8('\r\n\x1b[33m[Process exited: ' + (msg.payload?.code || 0) + ']\x1b[0m\r\n');
258
+ term.writeln('\n\x1b[33m[Process exited: ' + (msg.payload?.code || 0) + ']\x1b[0m\n');
239
259
  break;
240
260
  }
241
261
  }
@@ -250,7 +270,7 @@ export function getWebClient(): string {
250
270
  const value = cmdInput.value.trim();
251
271
  if (!value || !connected) return;
252
272
  cmdInput.value = '';
253
- term.io.writeUTF8('$ ' + value + '\r\n');
273
+ term.write('$ ' + value + '\r\n');
254
274
  ws.send(JSON.stringify({ type: 'terminal:input', data: value + '\r' }));
255
275
  setTimeout(() => cmdInput.focus(), 50);
256
276
  return false;
@@ -258,7 +278,7 @@ export function getWebClient(): string {
258
278
 
259
279
  function send(cmd) {
260
280
  if (!connected) return;
261
- term.io.writeUTF8('$ ' + cmd + '\r\n');
281
+ term.write('$ ' + cmd + '\r\n');
262
282
  ws.send(JSON.stringify({ type: 'terminal:input', data: cmd + '\r' }));
263
283
  }
264
284
 
@@ -272,6 +292,8 @@ export function getWebClient(): string {
272
292
  document.getElementById('terminal')?.addEventListener('click', () => {
273
293
  if (connected) cmdInput.focus();
274
294
  });
295
+
296
+ connect();
275
297
  </script>
276
298
  </body>
277
299
  </html>`;