nikcli-remote 1.0.1 → 1.0.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/dist/index.cjs CHANGED
@@ -5318,40 +5318,6 @@ var require_localtunnel = __commonJS({
5318
5318
  });
5319
5319
 
5320
5320
  // src/tunnel.ts
5321
- async function checkTunnelAvailability(provider) {
5322
- try {
5323
- const { execSync } = await import("child_process");
5324
- switch (provider) {
5325
- case "localtunnel":
5326
- try {
5327
- await Promise.resolve().then(() => __toESM(require_localtunnel(), 1));
5328
- return true;
5329
- } catch {
5330
- execSync("npx localtunnel --version", { stdio: "pipe" });
5331
- return true;
5332
- }
5333
- case "cloudflared":
5334
- execSync("cloudflared --version", { stdio: "pipe" });
5335
- return true;
5336
- case "ngrok":
5337
- execSync("ngrok version", { stdio: "pipe" });
5338
- return true;
5339
- default:
5340
- return false;
5341
- }
5342
- } catch {
5343
- return false;
5344
- }
5345
- }
5346
- async function findAvailableTunnel() {
5347
- const providers = ["localtunnel", "cloudflared", "ngrok"];
5348
- for (const provider of providers) {
5349
- if (await checkTunnelAvailability(provider)) {
5350
- return provider;
5351
- }
5352
- }
5353
- return null;
5354
- }
5355
5321
  var import_node_child_process2, TunnelManager;
5356
5322
  var init_tunnel = __esm({
5357
5323
  "src/tunnel.ts"() {
@@ -5422,7 +5388,7 @@ var init_tunnel = __esm({
5422
5388
  */
5423
5389
  createLocaltunnelCli(port) {
5424
5390
  return new Promise((resolve, reject) => {
5425
- this.process = (0, import_node_child_process2.spawn)("npx", ["localtunnel", "--port", port.toString()], {
5391
+ this.process = (0, import_node_child_process2.spawn)("npx", ["localtunnel", "--port", port.toString(), "--print-requests", "false"], {
5426
5392
  stdio: ["pipe", "pipe", "pipe"],
5427
5393
  shell: true
5428
5394
  });
@@ -5439,8 +5405,7 @@ var init_tunnel = __esm({
5439
5405
  resolve(match[1]);
5440
5406
  }
5441
5407
  });
5442
- this.process.stderr?.on("data", (data) => {
5443
- output += data.toString();
5408
+ this.process.stderr?.on("data", () => {
5444
5409
  });
5445
5410
  this.process.on("error", (error) => {
5446
5411
  clearTimeout(timeout);
@@ -5461,7 +5426,7 @@ var init_tunnel = __esm({
5461
5426
  return new Promise((resolve, reject) => {
5462
5427
  this.process = (0, import_node_child_process2.spawn)(
5463
5428
  "cloudflared",
5464
- ["tunnel", "--url", `http://localhost:${port}`],
5429
+ ["tunnel", "--url", `http://localhost:${port}`, "--metrics", "localhost:0"],
5465
5430
  {
5466
5431
  stdio: ["pipe", "pipe", "pipe"]
5467
5432
  }
@@ -5498,7 +5463,7 @@ var init_tunnel = __esm({
5498
5463
  */
5499
5464
  createNgrok(port) {
5500
5465
  return new Promise((resolve, reject) => {
5501
- this.process = (0, import_node_child_process2.spawn)("ngrok", ["http", port.toString(), "--log=stdout"], {
5466
+ this.process = (0, import_node_child_process2.spawn)("ngrok", ["http", port.toString(), "--log=stdout", "--log-level=info"], {
5502
5467
  stdio: ["pipe", "pipe", "pipe"]
5503
5468
  });
5504
5469
  let output = "";
@@ -5514,8 +5479,7 @@ var init_tunnel = __esm({
5514
5479
  resolve(match[1]);
5515
5480
  }
5516
5481
  });
5517
- this.process.stderr?.on("data", (data) => {
5518
- output += data.toString();
5482
+ this.process.stderr?.on("data", () => {
5519
5483
  });
5520
5484
  this.process.on("error", (error) => {
5521
5485
  clearTimeout(timeout);
@@ -5539,649 +5503,298 @@ function getWebClient() {
5539
5503
  <html lang="en">
5540
5504
  <head>
5541
5505
  <meta charset="UTF-8">
5542
- <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
5506
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover">
5543
5507
  <meta name="apple-mobile-web-app-capable" content="yes">
5544
- <meta name="mobile-web-app-capable" content="yes">
5545
- <meta name="theme-color" content="#0d1117">
5508
+ <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
5546
5509
  <title>NikCLI Remote</title>
5510
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/xterm@5.3.0/css/xterm.css" />
5547
5511
  <style>
5512
+ * { box-sizing: border-box; margin: 0; padding: 0; }
5548
5513
  :root {
5549
- --bg: #0d1117;
5514
+ --bg-primary: #0d1117;
5550
5515
  --bg-secondary: #161b22;
5551
- --fg: #e6edf3;
5552
- --fg-muted: #8b949e;
5553
5516
  --accent: #58a6ff;
5554
5517
  --success: #3fb950;
5555
- --warning: #d29922;
5556
- --error: #f85149;
5557
5518
  --border: #30363d;
5558
- --font-mono: 'SF Mono', 'Fira Code', 'Consolas', monospace;
5559
5519
  }
5560
-
5561
- * {
5562
- box-sizing: border-box;
5563
- margin: 0;
5564
- padding: 0;
5565
- -webkit-tap-highlight-color: transparent;
5566
- }
5567
-
5568
- html, body {
5569
- height: 100%;
5570
- background: var(--bg);
5571
- color: var(--fg);
5572
- font-family: var(--font-mono);
5573
- font-size: 14px;
5574
- overflow: hidden;
5575
- touch-action: manipulation;
5576
- }
5577
-
5578
- #app {
5520
+ html, body { height: 100%; overflow: hidden; touch-action: manipulation; }
5521
+ body {
5522
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
5523
+ background: var(--bg-primary);
5524
+ color: #e6edf3;
5579
5525
  display: flex;
5580
5526
  flex-direction: column;
5581
- height: 100%;
5582
- height: 100dvh;
5583
- }
5584
-
5585
- /* Header */
5586
- #header {
5587
- display: flex;
5588
- align-items: center;
5589
- justify-content: space-between;
5590
- padding: 12px 16px;
5591
- background: var(--bg-secondary);
5592
- border-bottom: 1px solid var(--border);
5593
- flex-shrink: 0;
5594
- }
5595
-
5596
- #header h1 {
5597
- font-size: 16px;
5598
- font-weight: 600;
5599
- color: var(--accent);
5600
- display: flex;
5601
- align-items: center;
5602
- gap: 8px;
5603
- }
5604
-
5605
- #header h1::before {
5606
- content: '';
5607
- width: 10px;
5608
- height: 10px;
5609
- background: var(--accent);
5610
- border-radius: 2px;
5611
- }
5612
-
5613
- #status {
5614
- display: flex;
5615
- align-items: center;
5616
- gap: 6px;
5617
- font-size: 12px;
5618
- color: var(--fg-muted);
5619
- }
5620
-
5621
- #status-dot {
5622
- width: 8px;
5623
- height: 8px;
5624
- border-radius: 50%;
5625
- background: var(--error);
5626
- transition: background 0.3s;
5627
- }
5628
-
5629
- #status-dot.connected {
5630
- background: var(--success);
5631
- }
5632
-
5633
- #status-dot.connecting {
5634
- background: var(--warning);
5635
- animation: pulse 1s infinite;
5636
- }
5637
-
5638
- @keyframes pulse {
5639
- 0%, 100% { opacity: 1; }
5640
- 50% { opacity: 0.5; }
5641
- }
5642
-
5643
- /* Terminal */
5644
- #terminal-container {
5645
- flex: 1;
5646
- overflow: hidden;
5647
- position: relative;
5648
- }
5649
-
5650
- #terminal {
5651
- height: 100%;
5652
- padding: 12px;
5653
- overflow-y: auto;
5654
- overflow-x: hidden;
5655
- font-size: 13px;
5656
- line-height: 1.5;
5657
- white-space: pre-wrap;
5658
- word-break: break-all;
5659
- -webkit-overflow-scrolling: touch;
5660
- }
5661
-
5662
- #terminal::-webkit-scrollbar {
5663
- width: 6px;
5664
- }
5665
-
5666
- #terminal::-webkit-scrollbar-track {
5667
- background: var(--bg);
5668
- }
5669
-
5670
- #terminal::-webkit-scrollbar-thumb {
5671
- background: var(--border);
5672
- border-radius: 3px;
5673
5527
  }
5674
-
5675
- .cursor {
5676
- display: inline-block;
5677
- width: 8px;
5678
- height: 16px;
5679
- background: var(--fg);
5680
- animation: blink 1s step-end infinite;
5681
- vertical-align: text-bottom;
5682
- }
5683
-
5684
- @keyframes blink {
5685
- 50% { opacity: 0; }
5686
- }
5687
-
5688
- /* Notifications */
5689
- #notifications {
5690
- position: fixed;
5691
- top: 60px;
5692
- left: 12px;
5693
- right: 12px;
5694
- z-index: 1000;
5695
- pointer-events: none;
5696
- }
5697
-
5698
- .notification {
5699
- background: var(--bg-secondary);
5700
- border: 1px solid var(--border);
5701
- border-radius: 8px;
5702
- padding: 12px 16px;
5703
- margin-bottom: 8px;
5704
- animation: slideIn 0.3s ease;
5705
- pointer-events: auto;
5706
- box-shadow: 0 4px 12px rgba(0,0,0,0.4);
5707
- }
5708
-
5709
- .notification.success { border-left: 3px solid var(--success); }
5710
- .notification.error { border-left: 3px solid var(--error); }
5711
- .notification.warning { border-left: 3px solid var(--warning); }
5712
- .notification.info { border-left: 3px solid var(--accent); }
5713
-
5714
- .notification h4 {
5715
- font-size: 14px;
5716
- font-weight: 600;
5717
- margin-bottom: 4px;
5718
- }
5719
-
5720
- .notification p {
5721
- font-size: 12px;
5722
- color: var(--fg-muted);
5723
- }
5724
-
5725
- @keyframes slideIn {
5726
- from { transform: translateY(-20px); opacity: 0; }
5727
- to { transform: translateY(0); opacity: 1; }
5728
- }
5729
-
5730
- /* Quick Keys */
5731
- #quickkeys {
5732
- display: grid;
5733
- grid-template-columns: repeat(6, 1fr);
5734
- gap: 6px;
5735
- padding: 8px 12px;
5736
- background: var(--bg-secondary);
5737
- border-top: 1px solid var(--border);
5738
- flex-shrink: 0;
5739
- }
5740
-
5741
- .qkey {
5742
- background: var(--bg);
5743
- border: 1px solid var(--border);
5744
- border-radius: 6px;
5745
- padding: 10px 4px;
5746
- color: var(--fg);
5747
- font-size: 11px;
5748
- font-family: var(--font-mono);
5749
- text-align: center;
5750
- cursor: pointer;
5751
- user-select: none;
5752
- transition: background 0.1s, transform 0.1s;
5753
- }
5754
-
5755
- .qkey:active {
5756
- background: var(--border);
5757
- transform: scale(0.95);
5758
- }
5759
-
5760
- .qkey.wide {
5761
- grid-column: span 2;
5762
- }
5763
-
5764
- .qkey.accent {
5765
- background: var(--accent);
5766
- border-color: var(--accent);
5767
- color: #fff;
5768
- }
5769
-
5770
- /* Input */
5771
- #input-container {
5772
- padding: 12px;
5528
+ #terminal { flex: 1; overflow: hidden; background: var(--bg-primary); padding: 8px; }
5529
+ #input-area {
5773
5530
  background: var(--bg-secondary);
5774
5531
  border-top: 1px solid var(--border);
5532
+ padding: 12px 16px;
5775
5533
  flex-shrink: 0;
5534
+ padding-bottom: env(safe-area-inset-bottom, 12px);
5776
5535
  }
5777
-
5778
- #input-row {
5779
- display: flex;
5780
- gap: 8px;
5781
- }
5782
-
5783
- #input {
5536
+ .input-row { display: flex; gap: 8px; align-items: center; }
5537
+ .prompt { color: var(--success); font-family: 'SF Mono', Monaco, monospace; font-size: 14px; white-space: nowrap; }
5538
+ #cmd-input {
5784
5539
  flex: 1;
5785
- background: var(--bg);
5540
+ background: #21262d;
5786
5541
  border: 1px solid var(--border);
5787
- border-radius: 8px;
5788
- padding: 12px 14px;
5789
- color: var(--fg);
5790
- font-family: var(--font-mono);
5542
+ border-radius: 12px;
5543
+ padding: 12px 16px;
5544
+ color: #e6edf3;
5791
5545
  font-size: 16px;
5546
+ font-family: 'SF Mono', Monaco, monospace;
5792
5547
  outline: none;
5793
- transition: border-color 0.2s;
5794
- }
5795
-
5796
- #input:focus {
5797
- border-color: var(--accent);
5798
- }
5799
-
5800
- #input::placeholder {
5801
- color: var(--fg-muted);
5548
+ -webkit-appearance: none;
5802
5549
  }
5803
-
5804
- #send {
5550
+ #cmd-input:focus { border-color: var(--accent); }
5551
+ #send-btn {
5805
5552
  background: var(--accent);
5806
- color: #fff;
5553
+ color: white;
5807
5554
  border: none;
5808
- border-radius: 8px;
5809
- padding: 12px 20px;
5810
- font-size: 14px;
5555
+ border-radius: 12px;
5556
+ padding: 12px 24px;
5557
+ font-size: 16px;
5811
5558
  font-weight: 600;
5812
- font-family: var(--font-mono);
5813
5559
  cursor: pointer;
5814
- transition: opacity 0.2s, transform 0.1s;
5815
- }
5816
-
5817
- #send:active {
5818
- opacity: 0.8;
5819
- transform: scale(0.98);
5560
+ -webkit-tap-highlight-color: transparent;
5820
5561
  }
5821
-
5822
- /* Auth Screen */
5823
- #auth-screen {
5824
- position: fixed;
5825
- inset: 0;
5826
- background: var(--bg);
5562
+ #send-btn:active { opacity: 0.7; }
5563
+ #status-bar {
5564
+ background: var(--bg-secondary);
5565
+ border-bottom: 1px solid var(--border);
5566
+ padding: 8px 16px;
5827
5567
  display: flex;
5828
- flex-direction: column;
5568
+ justify-content: space-between;
5829
5569
  align-items: center;
5830
- justify-content: center;
5831
- gap: 20px;
5832
- z-index: 2000;
5833
- }
5834
-
5835
- #auth-screen.hidden {
5836
- display: none;
5837
- }
5838
-
5839
- .spinner {
5840
- width: 40px;
5841
- height: 40px;
5842
- border: 3px solid var(--border);
5843
- border-top-color: var(--accent);
5844
- border-radius: 50%;
5845
- animation: spin 1s linear infinite;
5846
- }
5847
-
5848
- @keyframes spin {
5849
- to { transform: rotate(360deg); }
5850
- }
5851
-
5852
- #auth-screen p {
5853
- color: var(--fg-muted);
5854
- font-size: 14px;
5855
- }
5856
-
5857
- #auth-screen .error {
5858
- color: var(--error);
5859
- }
5860
-
5861
- /* Safe area padding for notched devices */
5862
- @supports (padding: env(safe-area-inset-bottom)) {
5863
- #input-container {
5864
- padding-bottom: calc(12px + env(safe-area-inset-bottom));
5865
- }
5866
- }
5867
-
5868
- /* Landscape adjustments */
5869
- @media (max-height: 500px) {
5870
- #quickkeys {
5871
- grid-template-columns: repeat(12, 1fr);
5872
- padding: 6px 8px;
5873
- }
5874
- .qkey {
5875
- padding: 8px 2px;
5876
- font-size: 10px;
5877
- }
5878
- #terminal {
5879
- font-size: 12px;
5880
- }
5570
+ font-size: 12px;
5571
+ padding-top: env(safe-area-inset-top, 8px);
5572
+ }
5573
+ .status-row { display: flex; align-items: center; gap: 8px; }
5574
+ .status-dot { width: 8px; height: 8px; border-radius: 50%; background: #8b949e; }
5575
+ .status-dot.connected { background: var(--success); box-shadow: 0 0 8px var(--success); }
5576
+ .status-dot.connecting { background: var(--accent); animation: pulse 1s infinite; }
5577
+ .status-dot.disconnected { background: #f85149; }
5578
+ @keyframes pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.5; } }
5579
+ #auth-overlay {
5580
+ position: fixed; inset: 0; background: var(--bg-primary);
5581
+ display: flex; flex-direction: column; align-items: center; justify-content: center;
5582
+ padding: 20px; z-index: 100;
5583
+ }
5584
+ #auth-overlay.hidden { display: none; }
5585
+ .auth-title { font-size: 28px; font-weight: 700; margin-bottom: 8px; }
5586
+ .auth-subtitle { color: #8b949e; font-size: 16px; margin-bottom: 24px; }
5587
+ .auth-msg {
5588
+ background: var(--bg-secondary); padding: 20px 28px;
5589
+ border-radius: 16px; border: 1px solid var(--border); text-align: center;
5590
+ }
5591
+ .auth-msg.error { color: #f85149; border-color: #f85149; }
5592
+ .quick-btns {
5593
+ display: flex; gap: 8px; margin-top: 20px; flex-wrap: wrap; justify-content: center;
5594
+ }
5595
+ .quick-btn {
5596
+ background: #21262d; border: 1px solid var(--border); color: #e6edf3;
5597
+ padding: 10px 16px; border-radius: 8px; font-size: 14px;
5598
+ font-family: 'SF Mono', Monaco, monospace; cursor: pointer;
5599
+ }
5600
+ .quick-btn:active { background: #30363d; }
5601
+ .hint { font-size: 12px; color: #8b949e; margin-top: 12px; }
5602
+ @media (max-width: 600px) {
5603
+ #input-area { padding: 10px 12px; }
5604
+ .quick-btn { padding: 8px 12px; font-size: 12px; }
5881
5605
  }
5882
5606
  </style>
5883
5607
  </head>
5884
5608
  <body>
5885
- <div id="app">
5886
- <div id="auth-screen">
5887
- <div class="spinner"></div>
5888
- <p id="auth-status">Connecting to NikCLI...</p>
5609
+ <div id="auth-overlay">
5610
+ <div class="auth-title">\u{1F4F1} NikCLI Remote</div>
5611
+ <div class="auth-subtitle">Full terminal emulation</div>
5612
+ <div id="auth-msg" class="auth-msg">
5613
+ <div id="auth-text">Connecting...</div>
5889
5614
  </div>
5890
-
5891
- <header id="header">
5892
- <h1>NikCLI Remote</h1>
5893
- <div id="status">
5894
- <span id="status-dot" class="connecting"></span>
5895
- <span id="status-text">Connecting</span>
5896
- </div>
5897
- </header>
5898
-
5899
- <div id="terminal-container">
5900
- <div id="terminal"></div>
5901
- </div>
5902
-
5903
- <div id="notifications"></div>
5904
-
5905
- <div id="quickkeys">
5906
- <button class="qkey" data-key="\\t">Tab</button>
5907
- <button class="qkey" data-key="\\x1b[A">\u2191</button>
5908
- <button class="qkey" data-key="\\x1b[B">\u2193</button>
5909
- <button class="qkey" data-key="\\x1b[D">\u2190</button>
5910
- <button class="qkey" data-key="\\x1b[C">\u2192</button>
5911
- <button class="qkey" data-key="\\x1b">Esc</button>
5912
- <button class="qkey" data-key="\\x03">^C</button>
5913
- <button class="qkey" data-key="\\x04">^D</button>
5914
- <button class="qkey" data-key="\\x1a">^Z</button>
5915
- <button class="qkey" data-key="\\x0c">^L</button>
5916
- <button class="qkey wide accent" data-key="\\r">Enter \u23CE</button>
5615
+ <div class="quick-btns">
5616
+ <button class="quick-btn" onclick="send('help')">/help</button>
5617
+ <button class="quick-btn" onclick="send('ls -la')">ls -la</button>
5618
+ <button class="quick-btn" onclick="send('pwd')">pwd</button>
5619
+ <button class="quick-btn" onclick="send('whoami')">whoami</button>
5620
+ <button class="quick-btn" onclick="send('clear')">clear</button>
5917
5621
  </div>
5622
+ <div class="hint">Mobile keyboard to type commands</div>
5623
+ </div>
5918
5624
 
5919
- <div id="input-container">
5920
- <div id="input-row">
5921
- <input type="text" id="input" placeholder="Type command..." autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false">
5922
- <button id="send">Send</button>
5923
- </div>
5625
+ <div id="status-bar">
5626
+ <div class="status-row">
5627
+ <span class="status-dot" id="status-dot"></span>
5628
+ <span id="status-text">Disconnected</span>
5924
5629
  </div>
5630
+ <span id="session-id" style="color: #8b949e;"></span>
5925
5631
  </div>
5926
5632
 
5927
- <script>
5928
- (function() {
5929
- 'use strict';
5930
-
5931
- // Parse URL params
5932
- const params = new URLSearchParams(location.search);
5933
- const token = params.get('t');
5934
- const sessionId = params.get('s');
5935
-
5936
- // DOM elements
5937
- const terminal = document.getElementById('terminal');
5938
- const input = document.getElementById('input');
5939
- const sendBtn = document.getElementById('send');
5940
- const statusDot = document.getElementById('status-dot');
5941
- const statusText = document.getElementById('status-text');
5942
- const authScreen = document.getElementById('auth-screen');
5943
- const authStatus = document.getElementById('auth-status');
5944
- const notifications = document.getElementById('notifications');
5945
-
5946
- // State
5947
- let ws = null;
5948
- let reconnectAttempts = 0;
5949
- const maxReconnectAttempts = 5;
5950
- let terminalEnabled = true;
5951
-
5952
- // Connect to WebSocket
5953
- function connect() {
5954
- const protocol = location.protocol === 'https:' ? 'wss:' : 'ws:';
5955
- ws = new WebSocket(protocol + '//' + location.host);
5956
-
5957
- ws.onopen = function() {
5958
- setStatus('connecting', 'Authenticating...');
5959
- ws.send(JSON.stringify({ type: 'auth', token: token }));
5960
- };
5961
-
5962
- ws.onmessage = function(event) {
5963
- try {
5964
- const msg = JSON.parse(event.data);
5965
- handleMessage(msg);
5966
- } catch (e) {
5967
- console.error('Parse error:', e);
5968
- }
5969
- };
5970
-
5971
- ws.onclose = function() {
5972
- setStatus('disconnected', 'Disconnected');
5973
- if (reconnectAttempts < maxReconnectAttempts) {
5974
- reconnectAttempts++;
5975
- const delay = Math.min(2000 * reconnectAttempts, 10000);
5976
- setTimeout(connect, delay);
5977
- } else {
5978
- authStatus.textContent = 'Connection failed. Refresh to retry.';
5979
- authStatus.classList.add('error');
5980
- authScreen.classList.remove('hidden');
5981
- }
5982
- };
5983
-
5984
- ws.onerror = function() {
5985
- console.error('WebSocket error');
5986
- };
5987
- }
5988
-
5989
- // Handle incoming message
5990
- function handleMessage(msg) {
5991
- switch (msg.type) {
5992
- case 'auth:required':
5993
- // Already sent auth on open
5994
- break;
5995
-
5996
- case 'auth:success':
5997
- authScreen.classList.add('hidden');
5998
- setStatus('connected', 'Connected');
5999
- reconnectAttempts = 0;
6000
- terminalEnabled = msg.payload?.terminalEnabled !== false;
6001
- if (terminalEnabled) {
6002
- appendOutput('\\x1b[32mConnected to NikCLI\\x1b[0m\\n\\n');
6003
- }
6004
- break;
6005
-
6006
- case 'auth:failed':
6007
- authStatus.textContent = 'Authentication failed';
6008
- authStatus.classList.add('error');
6009
- break;
6010
-
6011
- case 'terminal:output':
6012
- if (msg.payload?.data) {
6013
- appendOutput(msg.payload.data);
6014
- }
6015
- break;
6016
-
6017
- case 'terminal:exit':
6018
- appendOutput('\\n\\x1b[33m[Process exited with code ' + (msg.payload?.code || 0) + ']\\x1b[0m\\n');
6019
- break;
6020
-
6021
- case 'notification':
6022
- showNotification(msg.payload);
6023
- break;
6024
-
6025
- case 'session:end':
6026
- appendOutput('\\n\\x1b[31m[Session ended]\\x1b[0m\\n');
6027
- setStatus('disconnected', 'Session ended');
6028
- break;
6029
-
6030
- case 'pong':
6031
- // Heartbeat response
6032
- break;
5633
+ <div id="terminal"></div>
6033
5634
 
6034
- default:
6035
- console.log('Unknown message:', msg.type);
6036
- }
6037
- }
5635
+ <div id="input-area">
5636
+ <form class="input-row" onsubmit="return handleSubmit(event)">
5637
+ <span class="prompt">$</span>
5638
+ <input type="text" id="cmd-input" placeholder="Type command..." autocomplete="off" enterkeyhint="send" inputmode="text">
5639
+ <button type="submit" id="send-btn">Send</button>
5640
+ </form>
5641
+ </div>
6038
5642
 
6039
- // Append text to terminal with ANSI support
6040
- function appendOutput(text) {
6041
- // Simple ANSI to HTML conversion
6042
- const html = ansiToHtml(text);
6043
- terminal.innerHTML += html;
6044
- terminal.scrollTop = terminal.scrollHeight;
6045
- }
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>
5645
+ <script>
5646
+ let ws = null, term = null, fitAddon = null, connected = false, reconnectAttempts = 0;
5647
+ const token = new URLSearchParams(location.search).get('t') || '';
5648
+ const sessionId = new URLSearchParams(location.search).get('s') || '';
5649
+
5650
+ const authOverlay = document.getElementById('auth-overlay');
5651
+ const authMsg = document.getElementById('auth-msg');
5652
+ const authText = document.getElementById('auth-text');
5653
+ const statusDot = document.getElementById('status-dot');
5654
+ const statusText = document.getElementById('status-text');
5655
+ const sessionSpan = document.getElementById('session-id');
5656
+ const cmdInput = document.getElementById('cmd-input');
5657
+
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
+ });
6046
5687
 
6047
- // Basic ANSI to HTML
6048
- function ansiToHtml(text) {
6049
- const ansiColors = {
6050
- '30': '#6e7681', '31': '#f85149', '32': '#3fb950', '33': '#d29922',
6051
- '34': '#58a6ff', '35': '#bc8cff', '36': '#76e3ea', '37': '#e6edf3',
6052
- '90': '#6e7681', '91': '#f85149', '92': '#3fb950', '93': '#d29922',
6053
- '94': '#58a6ff', '95': '#bc8cff', '96': '#76e3ea', '97': '#ffffff'
6054
- };
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');
5694
+
5695
+ // Resize handler
5696
+ window.addEventListener('resize', () => {
5697
+ clearTimeout(window.resizeTimer);
5698
+ window.resizeTimer = setTimeout(() => fitAddon.fit(), 100);
5699
+ });
6055
5700
 
6056
- let result = '';
6057
- let currentStyle = '';
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
+ }
6058
5708
 
6059
- const parts = text.split(/\\x1b\\[([0-9;]+)m/);
6060
- for (let i = 0; i < parts.length; i++) {
6061
- if (i % 2 === 0) {
6062
- // Text content
6063
- result += escapeHtml(parts[i]);
6064
- } else {
6065
- // ANSI code
6066
- const codes = parts[i].split(';');
6067
- for (const code of codes) {
6068
- if (code === '0') {
6069
- if (currentStyle) {
6070
- result += '</span>';
6071
- currentStyle = '';
6072
- }
6073
- } else if (code === '1') {
6074
- currentStyle = 'font-weight:bold;';
6075
- result += '<span style="' + currentStyle + '">';
6076
- } else if (ansiColors[code]) {
6077
- if (currentStyle) result += '</span>';
6078
- currentStyle = 'color:' + ansiColors[code] + ';';
6079
- result += '<span style="' + currentStyle + '">';
6080
- }
6081
- }
6082
- }
6083
- }
5709
+ function connect() {
5710
+ const protocol = location.protocol === 'https:' ? 'wss:' : 'ws:';
5711
+ ws = new WebSocket(protocol + '//' + location.host + '/ws');
6084
5712
 
6085
- if (currentStyle) result += '</span>';
6086
- return result;
6087
- }
5713
+ ws.onopen = () => {
5714
+ setStatus('connecting', 'Authenticating...');
5715
+ ws.send(JSON.stringify({ type: 'auth', token }));
5716
+ reconnectAttempts = 0;
5717
+ };
6088
5718
 
6089
- // Escape HTML
6090
- function escapeHtml(text) {
6091
- return text
6092
- .replace(/&/g, '&amp;')
6093
- .replace(/</g, '&lt;')
6094
- .replace(/>/g, '&gt;')
6095
- .replace(/"/g, '&quot;')
6096
- .replace(/\\n/g, '<br>')
6097
- .replace(/ /g, '&nbsp;');
6098
- }
5719
+ ws.onmessage = (e) => {
5720
+ try { handleMessage(JSON.parse(e.data)); } catch (err) { console.error(err); }
5721
+ };
6099
5722
 
6100
- // Set connection status
6101
- function setStatus(state, text) {
6102
- statusDot.className = state === 'connected' ? 'connected' :
6103
- state === 'connecting' ? 'connecting' : '';
6104
- statusText.textContent = text;
6105
- }
5723
+ ws.onclose = () => {
5724
+ setStatus('disconnected', 'Disconnected');
5725
+ connected = false;
5726
+ reconnectAttempts++;
5727
+ setTimeout(connect, Math.min(3000, reconnectAttempts * 500));
5728
+ };
6106
5729
 
6107
- // Send data to terminal
6108
- function send(data) {
6109
- if (ws && ws.readyState === WebSocket.OPEN) {
6110
- ws.send(JSON.stringify({ type: 'terminal:input', data: data }));
6111
- }
6112
- }
5730
+ ws.onerror = () => setStatus('disconnected', 'Connection error');
5731
+ }
6113
5732
 
6114
- // Show notification
6115
- function showNotification(n) {
6116
- if (!n) return;
6117
- const el = document.createElement('div');
6118
- el.className = 'notification ' + (n.type || 'info');
6119
- el.innerHTML = '<h4>' + escapeHtml(n.title || 'Notification') + '</h4>' +
6120
- '<p>' + escapeHtml(n.body || '') + '</p>';
6121
- notifications.appendChild(el);
6122
- setTimeout(function() { el.remove(); }, 5000);
5733
+ function handleMessage(msg) {
5734
+ switch (msg.type) {
5735
+ case 'auth:required':
5736
+ ws.send(JSON.stringify({ type: 'auth', token }));
5737
+ break;
5738
+ case 'auth:success':
5739
+ connected = true;
5740
+ authOverlay.classList.add('hidden');
5741
+ setStatus('connected', 'Connected');
5742
+ sessionSpan.textContent = sessionId ? 'Session: ' + sessionId : '';
5743
+ term.writeln('
5744
+ \x1B[32m\u2713 Connected to NikCLI\x1B[0m
5745
+ ');
5746
+ break;
5747
+ case 'auth:failed':
5748
+ authMsg.classList.add('error');
5749
+ authText.textContent = 'Authentication failed';
5750
+ break;
5751
+ case 'terminal:output':
5752
+ if (msg.payload?.data) term.write(msg.payload.data);
5753
+ break;
5754
+ case 'terminal:exit':
5755
+ term.writeln('
5756
+ \x1B[33m[Process exited: ' + (msg.payload?.code || 0) + ']\x1B[0m
5757
+ ');
5758
+ break;
6123
5759
  }
5760
+ }
6124
5761
 
6125
- // Event: Send button
6126
- sendBtn.onclick = function() {
6127
- if (input.value) {
6128
- send(input.value + '\\r');
6129
- input.value = '';
6130
- }
6131
- input.focus();
6132
- };
6133
-
6134
- // Event: Enter key in input
6135
- input.onkeydown = function(e) {
6136
- if (e.key === 'Enter') {
6137
- e.preventDefault();
6138
- sendBtn.click();
6139
- }
6140
- };
5762
+ function setStatus(state, text) {
5763
+ statusDot.className = 'status-dot ' + state;
5764
+ statusText.textContent = text;
5765
+ }
6141
5766
 
6142
- // Event: Quick keys
6143
- document.querySelectorAll('.qkey').forEach(function(btn) {
6144
- btn.onclick = function() {
6145
- const key = btn.dataset.key;
6146
- const decoded = key
6147
- .replace(/\\\\x([0-9a-f]{2})/gi, function(_, hex) {
6148
- return String.fromCharCode(parseInt(hex, 16));
6149
- })
6150
- .replace(/\\\\t/g, '\\t')
6151
- .replace(/\\\\r/g, '\\r')
6152
- .replace(/\\\\n/g, '\\n');
6153
- send(decoded);
6154
- input.focus();
6155
- };
6156
- });
5767
+ function handleSubmit(e) {
5768
+ e?.preventDefault();
5769
+ const value = cmdInput.value.trim();
5770
+ if (!value || !connected) return;
5771
+ cmdInput.value = '';
5772
+ term.write('$ ' + value + '\r
5773
+ ');
5774
+ ws.send(JSON.stringify({ type: 'terminal:input', data: value + '\r' }));
5775
+ setTimeout(() => cmdInput.focus(), 50);
5776
+ return false;
5777
+ }
6157
5778
 
6158
- // Heartbeat
6159
- setInterval(function() {
6160
- if (ws && ws.readyState === WebSocket.OPEN) {
6161
- ws.send(JSON.stringify({ type: 'ping' }));
6162
- }
6163
- }, 25000);
5779
+ function send(cmd) {
5780
+ if (!connected) return;
5781
+ term.write('$ ' + cmd + '\r
5782
+ ');
5783
+ ws.send(JSON.stringify({ type: 'terminal:input', data: cmd + '\r' }));
5784
+ }
6164
5785
 
6165
- // Handle resize
6166
- function sendResize() {
6167
- if (ws && ws.readyState === WebSocket.OPEN) {
6168
- const cols = Math.floor(terminal.clientWidth / 8);
6169
- const rows = Math.floor(terminal.clientHeight / 18);
6170
- ws.send(JSON.stringify({ type: 'terminal:resize', cols: cols, rows: rows }));
6171
- }
5786
+ cmdInput.addEventListener('keydown', (e) => {
5787
+ if (e.key === 'Enter' && !e.shiftKey) {
5788
+ e.preventDefault();
5789
+ handleSubmit();
6172
5790
  }
5791
+ });
6173
5792
 
6174
- window.addEventListener('resize', sendResize);
6175
- setTimeout(sendResize, 1000);
5793
+ document.getElementById('terminal')?.addEventListener('click', () => {
5794
+ if (connected) cmdInput.focus();
5795
+ });
6176
5796
 
6177
- // Start connection
6178
- if (token) {
6179
- connect();
6180
- } else {
6181
- authStatus.textContent = 'Invalid session URL';
6182
- authStatus.classList.add('error');
6183
- }
6184
- })();
5797
+ connect();
6185
5798
  </script>
6186
5799
  </body>
6187
5800
  </html>`;
@@ -6283,6 +5896,7 @@ var init_server = __esm({
6283
5896
  });
6284
5897
  this.terminal.on("data", (data) => {
6285
5898
  this.broadcast({ type: MessageTypes.TERMINAL_OUTPUT, payload: { data } });
5899
+ this.emit("terminal:output", data);
6286
5900
  });
6287
5901
  this.terminal.on("exit", (code) => {
6288
5902
  this.broadcast({ type: MessageTypes.TERMINAL_EXIT, payload: { code } });
@@ -6499,9 +6113,6 @@ var init_server = __esm({
6499
6113
  * Handle authentication
6500
6114
  */
6501
6115
  handleAuth(client, token) {
6502
- console.log(`[Tunnel] Auth attempt from ${client.id}, token length: ${token?.length || 0}`);
6503
- console.log(`[Tunnel] Expected secret: ${this.sessionSecret?.substring(0, 8)}...`);
6504
- console.log(`[Tunnel] Received token: ${token?.substring(0, 8)}...`);
6505
6116
  if (token === this.sessionSecret) {
6506
6117
  client.authenticated = true;
6507
6118
  if (this.session) {
@@ -6522,9 +6133,7 @@ var init_server = __esm({
6522
6133
  this.terminal?.start();
6523
6134
  }
6524
6135
  this.emit("client:connected", client.device);
6525
- console.log(`[Tunnel] Auth success for ${client.id}`);
6526
6136
  } else {
6527
- console.log(`[Tunnel] Auth failed for ${client.id}`);
6528
6137
  client.ws.send(JSON.stringify({ type: MessageTypes.AUTH_FAILED, timestamp: Date.now() }));
6529
6138
  setTimeout(() => client.ws.close(1008, "Authentication failed"), 100);
6530
6139
  }
@@ -6648,141 +6257,12 @@ __export(index_exports, {
6648
6257
  MessageTypes: () => MessageTypes,
6649
6258
  RemoteServer: () => RemoteServer,
6650
6259
  TerminalManager: () => TerminalManager,
6651
- TunnelManager: () => TunnelManager,
6652
- checkTunnelAvailability: () => checkTunnelAvailability,
6653
6260
  createRemoteServer: () => createRemoteServer,
6654
- findAvailableTunnel: () => findAvailableTunnel,
6655
- generateQR: () => generateQR,
6656
- generateQRDataURL: () => generateQRDataURL,
6657
- getWebClient: () => getWebClient,
6658
- progressBar: () => progressBar,
6659
- renderSessionCard: () => renderSessionCard
6261
+ getWebClient: () => getWebClient
6660
6262
  });
6661
6263
  module.exports = __toCommonJS(index_exports);
6662
6264
  init_server();
6663
6265
  init_terminal();
6664
- init_tunnel();
6665
-
6666
- // src/qrcode.ts
6667
- var QRCode = null;
6668
- try {
6669
- QRCode = require("qrcode");
6670
- } catch {
6671
- }
6672
- async function generateQR(url, options = {}) {
6673
- if (!QRCode) {
6674
- return generateFallbackQR(url);
6675
- }
6676
- try {
6677
- const qrString = await QRCode.toString(url, {
6678
- type: "terminal",
6679
- small: options.small ?? true,
6680
- margin: options.margin ?? 1
6681
- });
6682
- return qrString;
6683
- } catch {
6684
- return generateFallbackQR(url);
6685
- }
6686
- }
6687
- async function generateQRDataURL(url) {
6688
- if (!QRCode) return null;
6689
- try {
6690
- return await QRCode.toDataURL(url, {
6691
- margin: 2,
6692
- width: 256,
6693
- color: {
6694
- dark: "#000000",
6695
- light: "#ffffff"
6696
- }
6697
- });
6698
- } catch {
6699
- return null;
6700
- }
6701
- }
6702
- function generateFallbackQR(url) {
6703
- return `
6704
- \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510
6705
- \u2502 \u2502
6706
- \u2502 QR Code generation unavailable \u2502
6707
- \u2502 \u2502
6708
- \u2502 Install 'qrcode' package or \u2502
6709
- \u2502 visit the URL directly: \u2502
6710
- \u2502 \u2502
6711
- \u2502 ${url.substring(0, 35)}${url.length > 35 ? "..." : ""}
6712
- \u2502 \u2502
6713
- \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518
6714
- `;
6715
- }
6716
- async function renderSessionCard(session) {
6717
- const qr = await generateQR(session.qrUrl);
6718
- const statusIcon = getStatusIcon(session.status);
6719
- const statusColor = getStatusColor(session.status);
6720
- const lines = [
6721
- "",
6722
- "\u256D\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256E",
6723
- "\u2502 NikCLI Remote Session \u2502",
6724
- "\u2570\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256F",
6725
- ""
6726
- ];
6727
- const qrLines = qr.split("\n").filter((l) => l.trim());
6728
- for (const line of qrLines) {
6729
- lines.push(" " + line);
6730
- }
6731
- lines.push("");
6732
- lines.push("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
6733
- lines.push("");
6734
- lines.push(` Session: ${session.id}`);
6735
- lines.push(` Status: ${statusColor}${statusIcon} ${session.status}\x1B[0m`);
6736
- lines.push(` Devices: ${session.connectedDevices.length} connected`);
6737
- lines.push("");
6738
- if (session.tunnelUrl) {
6739
- lines.push(` \x1B[36mPublic URL:\x1B[0m`);
6740
- lines.push(` ${session.tunnelUrl}`);
6741
- } else {
6742
- lines.push(` \x1B[36mLocal URL:\x1B[0m`);
6743
- lines.push(` ${session.localUrl}`);
6744
- }
6745
- lines.push("");
6746
- lines.push(` \x1B[90mScan QR code or open URL on your phone\x1B[0m`);
6747
- lines.push("");
6748
- lines.push("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
6749
- lines.push(" [q] Stop [r] Refresh [c] Copy URL");
6750
- lines.push("");
6751
- return lines.join("\n");
6752
- }
6753
- function getStatusIcon(status) {
6754
- const icons = {
6755
- starting: "\u25EF",
6756
- waiting: "\u25C9",
6757
- connected: "\u25CF",
6758
- stopped: "\u25CB",
6759
- error: "\u2716"
6760
- };
6761
- return icons[status] || "?";
6762
- }
6763
- function getStatusColor(status) {
6764
- const colors = {
6765
- starting: "\x1B[33m",
6766
- // Yellow
6767
- waiting: "\x1B[33m",
6768
- // Yellow
6769
- connected: "\x1B[32m",
6770
- // Green
6771
- stopped: "\x1B[90m",
6772
- // Gray
6773
- error: "\x1B[31m"
6774
- // Red
6775
- };
6776
- return colors[status] || "";
6777
- }
6778
- function progressBar(current, total, width = 30) {
6779
- const percent = Math.round(current / total * 100);
6780
- const filled = Math.round(current / total * width);
6781
- const empty = width - filled;
6782
- return `[${"\u2588".repeat(filled)}${"\u2591".repeat(empty)}] ${percent}%`;
6783
- }
6784
-
6785
- // src/index.ts
6786
6266
  init_web_client();
6787
6267
  init_types();
6788
6268
  async function createRemoteServer(config = {}) {
@@ -6797,13 +6277,6 @@ async function createRemoteServer(config = {}) {
6797
6277
  MessageTypes,
6798
6278
  RemoteServer,
6799
6279
  TerminalManager,
6800
- TunnelManager,
6801
- checkTunnelAvailability,
6802
6280
  createRemoteServer,
6803
- findAvailableTunnel,
6804
- generateQR,
6805
- generateQRDataURL,
6806
- getWebClient,
6807
- progressBar,
6808
- renderSessionCard
6281
+ getWebClient
6809
6282
  });