nikcli-remote 1.0.1 → 1.0.2

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,278 @@ 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>
5547
5510
  <style>
5511
+ * { box-sizing: border-box; margin: 0; padding: 0; }
5548
5512
  :root {
5549
- --bg: #0d1117;
5513
+ --bg-primary: #0d1117;
5550
5514
  --bg-secondary: #161b22;
5551
- --fg: #e6edf3;
5552
- --fg-muted: #8b949e;
5553
5515
  --accent: #58a6ff;
5554
5516
  --success: #3fb950;
5555
- --warning: #d29922;
5556
- --error: #f85149;
5557
5517
  --border: #30363d;
5558
- --font-mono: 'SF Mono', 'Fira Code', 'Consolas', monospace;
5559
5518
  }
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 {
5519
+ html, body { height: 100%; overflow: hidden; touch-action: manipulation; }
5520
+ body {
5521
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
5522
+ background: var(--bg-primary);
5523
+ color: #e6edf3;
5579
5524
  display: flex;
5580
5525
  flex-direction: column;
5581
- height: 100%;
5582
- height: 100dvh;
5583
5526
  }
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
- }
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;
5527
+ #terminal { flex: 1; overflow: hidden; background: var(--bg-primary); }
5528
+ #input-area {
5773
5529
  background: var(--bg-secondary);
5774
5530
  border-top: 1px solid var(--border);
5531
+ padding: 12px 16px;
5775
5532
  flex-shrink: 0;
5533
+ padding-bottom: env(safe-area-inset-bottom, 12px);
5776
5534
  }
5777
-
5778
- #input-row {
5779
- display: flex;
5780
- gap: 8px;
5781
- }
5782
-
5783
- #input {
5535
+ .input-row { display: flex; gap: 8px; align-items: center; }
5536
+ .prompt { color: var(--success); font-family: 'SF Mono', Monaco, monospace; font-size: 14px; white-space: nowrap; }
5537
+ #cmd-input {
5784
5538
  flex: 1;
5785
- background: var(--bg);
5539
+ background: #21262d;
5786
5540
  border: 1px solid var(--border);
5787
- border-radius: 8px;
5788
- padding: 12px 14px;
5789
- color: var(--fg);
5790
- font-family: var(--font-mono);
5541
+ border-radius: 12px;
5542
+ padding: 12px 16px;
5543
+ color: #e6edf3;
5791
5544
  font-size: 16px;
5545
+ font-family: 'SF Mono', Monaco, monospace;
5792
5546
  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);
5547
+ -webkit-appearance: none;
5802
5548
  }
5803
-
5804
- #send {
5549
+ #cmd-input:focus { border-color: var(--accent); }
5550
+ #send-btn {
5805
5551
  background: var(--accent);
5806
- color: #fff;
5552
+ color: white;
5807
5553
  border: none;
5808
- border-radius: 8px;
5809
- padding: 12px 20px;
5810
- font-size: 14px;
5554
+ border-radius: 12px;
5555
+ padding: 12px 24px;
5556
+ font-size: 16px;
5811
5557
  font-weight: 600;
5812
- font-family: var(--font-mono);
5813
5558
  cursor: pointer;
5814
- transition: opacity 0.2s, transform 0.1s;
5815
- }
5816
-
5817
- #send:active {
5818
- opacity: 0.8;
5819
- transform: scale(0.98);
5559
+ -webkit-tap-highlight-color: transparent;
5820
5560
  }
5821
-
5822
- /* Auth Screen */
5823
- #auth-screen {
5824
- position: fixed;
5825
- inset: 0;
5826
- background: var(--bg);
5561
+ #send-btn:active { opacity: 0.7; }
5562
+ #status-bar {
5563
+ background: var(--bg-secondary);
5564
+ border-bottom: 1px solid var(--border);
5565
+ padding: 8px 16px;
5827
5566
  display: flex;
5828
- flex-direction: column;
5567
+ justify-content: space-between;
5829
5568
  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
- }
5569
+ font-size: 12px;
5570
+ padding-top: env(safe-area-inset-top, 8px);
5571
+ }
5572
+ .status-row { display: flex; align-items: center; gap: 8px; }
5573
+ .status-dot { width: 8px; height: 8px; border-radius: 50%; background: #8b949e; }
5574
+ .status-dot.connected { background: var(--success); box-shadow: 0 0 8px var(--success); }
5575
+ .status-dot.connecting { background: var(--accent); animation: pulse 1s infinite; }
5576
+ .status-dot.disconnected { background: #f85149; }
5577
+ @keyframes pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.5; } }
5578
+ #auth-overlay {
5579
+ position: fixed; inset: 0; background: var(--bg-primary);
5580
+ display: flex; flex-direction: column; align-items: center; justify-content: center;
5581
+ padding: 20px; z-index: 100;
5582
+ }
5583
+ #auth-overlay.hidden { display: none; }
5584
+ .auth-title { font-size: 28px; font-weight: 700; margin-bottom: 8px; }
5585
+ .auth-subtitle { color: #8b949e; font-size: 16px; margin-bottom: 24px; }
5586
+ .auth-msg {
5587
+ background: var(--bg-secondary); padding: 20px 28px;
5588
+ border-radius: 16px; border: 1px solid var(--border); text-align: center;
5589
+ }
5590
+ .auth-msg.error { color: #f85149; border-color: #f85149; }
5591
+ .quick-btns {
5592
+ display: flex; gap: 8px; margin-top: 20px; flex-wrap: wrap; justify-content: center;
5593
+ }
5594
+ .quick-btn {
5595
+ background: #21262d; border: 1px solid var(--border); color: #e6edf3;
5596
+ padding: 10px 16px; border-radius: 8px; font-size: 14px;
5597
+ font-family: 'SF Mono', Monaco, monospace; cursor: pointer;
5598
+ }
5599
+ .quick-btn:active { background: #30363d; }
5600
+ .hint { font-size: 12px; color: #8b949e; margin-top: 12px; }
5601
+ @media (max-width: 600px) {
5602
+ #input-area { padding: 10px 12px; }
5603
+ .quick-btn { padding: 8px 12px; font-size: 12px; }
5881
5604
  }
5882
5605
  </style>
5883
5606
  </head>
5884
5607
  <body>
5885
- <div id="app">
5886
- <div id="auth-screen">
5887
- <div class="spinner"></div>
5888
- <p id="auth-status">Connecting to NikCLI...</p>
5889
- </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>
5608
+ <div id="auth-overlay">
5609
+ <div class="auth-title">\u{1F4F1} NikCLI Remote</div>
5610
+ <div class="auth-subtitle">Full terminal emulation</div>
5611
+ <div id="auth-msg" class="auth-msg">
5612
+ <div id="auth-text">Connecting...</div>
5901
5613
  </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>
5614
+ <div class="quick-btns">
5615
+ <button class="quick-btn" onclick="send('help')">/help</button>
5616
+ <button class="quick-btn" onclick="send('ls -la')">ls -la</button>
5617
+ <button class="quick-btn" onclick="send('pwd')">pwd</button>
5618
+ <button class="quick-btn" onclick="send('whoami')">whoami</button>
5619
+ <button class="quick-btn" onclick="send('clear')">clear</button>
5917
5620
  </div>
5621
+ <div class="hint">Mobile keyboard to type commands</div>
5622
+ </div>
5918
5623
 
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>
5624
+ <div id="status-bar">
5625
+ <div class="status-row">
5626
+ <span class="status-dot" id="status-dot"></span>
5627
+ <span id="status-text">Disconnected</span>
5924
5628
  </div>
5629
+ <span id="session-id" style="color: #8b949e;"></span>
5925
5630
  </div>
5926
5631
 
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);
5632
+ <div id="terminal"></div>
5956
5633
 
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;
5634
+ <div id="input-area">
5635
+ <form class="input-row" onsubmit="return handleSubmit(event)">
5636
+ <span class="prompt">$</span>
5637
+ <input type="text" id="cmd-input" placeholder="Type command..." autocomplete="off" enterkeyhint="send" inputmode="text">
5638
+ <button type="submit" id="send-btn">Send</button>
5639
+ </form>
5640
+ </div>
6033
5641
 
6034
- default:
6035
- console.log('Unknown message:', msg.type);
6036
- }
6037
- }
5642
+ <!-- hterm from Google's Chromium -->
5643
+ <script src="https://cdn.jsdelivr.net/npm/hterm@1.1.1/lib/hterm.js"></script>
5644
+ <script>
5645
+ let ws = null, term = null, connected = false, reconnectAttempts = 0;
5646
+ const token = new URLSearchParams(location.search).get('t') || '';
5647
+ const sessionId = new URLSearchParams(location.search).get('s') || '';
5648
+
5649
+ const authOverlay = document.getElementById('auth-overlay');
5650
+ const authMsg = document.getElementById('auth-msg');
5651
+ const authText = document.getElementById('auth-text');
5652
+ const statusDot = document.getElementById('status-dot');
5653
+ const statusText = document.getElementById('status-text');
5654
+ const sessionSpan = document.getElementById('session-id');
5655
+ const cmdInput = document.getElementById('cmd-input');
5656
+
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);
5671
+
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();
6038
5682
 
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;
5683
+ // Resize handler
5684
+ window.addEventListener('resize', () => {
5685
+ const el = document.getElementById('terminal');
5686
+ if (term && el) {
5687
+ term.setHeightAndWidth(el.clientHeight / 17, el.clientWidth / 8.53);
6045
5688
  }
5689
+ });
6046
5690
 
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
- };
6055
-
6056
- let result = '';
6057
- let currentStyle = '';
6058
-
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
- }
5691
+ function connect() {
5692
+ const protocol = location.protocol === 'https:' ? 'wss:' : 'ws:';
5693
+ ws = new WebSocket(protocol + '//' + location.host + '/ws');
6084
5694
 
6085
- if (currentStyle) result += '</span>';
6086
- return result;
6087
- }
5695
+ ws.onopen = () => {
5696
+ setStatus('connecting', 'Authenticating...');
5697
+ ws.send(JSON.stringify({ type: 'auth', token }));
5698
+ reconnectAttempts = 0;
5699
+ };
6088
5700
 
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
- }
5701
+ ws.onmessage = (e) => {
5702
+ try { handleMessage(JSON.parse(e.data)); } catch (err) { console.error(err); }
5703
+ };
6099
5704
 
6100
- // Set connection status
6101
- function setStatus(state, text) {
6102
- statusDot.className = state === 'connected' ? 'connected' :
6103
- state === 'connecting' ? 'connecting' : '';
6104
- statusText.textContent = text;
6105
- }
5705
+ ws.onclose = () => {
5706
+ setStatus('disconnected', 'Disconnected');
5707
+ connected = false;
5708
+ reconnectAttempts++;
5709
+ setTimeout(connect, Math.min(3000, reconnectAttempts * 500));
5710
+ };
6106
5711
 
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
- }
5712
+ ws.onerror = () => setStatus('disconnected', 'Connection error');
5713
+ }
6113
5714
 
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);
5715
+ function handleMessage(msg) {
5716
+ switch (msg.type) {
5717
+ case 'auth:required':
5718
+ ws.send(JSON.stringify({ type: 'auth', token }));
5719
+ break;
5720
+ case 'auth:success':
5721
+ connected = true;
5722
+ authOverlay.classList.add('hidden');
5723
+ setStatus('connected', 'Connected');
5724
+ sessionSpan.textContent = sessionId ? 'Session: ' + sessionId : '';
5725
+ term.io.writeUTF8('\r
5726
+ \x1B[32m\u2713 Connected to NikCLI\x1B[0m\r
5727
+ ');
5728
+ break;
5729
+ case 'auth:failed':
5730
+ authMsg.classList.add('error');
5731
+ authText.textContent = 'Authentication failed';
5732
+ break;
5733
+ case 'terminal:output':
5734
+ if (msg.payload?.data) term.io.writeUTF8(msg.payload.data);
5735
+ break;
5736
+ case 'terminal:exit':
5737
+ term.io.writeUTF8('\r
5738
+ \x1B[33m[Process exited: ' + (msg.payload?.code || 0) + ']\x1B[0m\r
5739
+ ');
5740
+ break;
6123
5741
  }
5742
+ }
6124
5743
 
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
- };
5744
+ function setStatus(state, text) {
5745
+ statusDot.className = 'status-dot ' + state;
5746
+ statusText.textContent = text;
5747
+ }
6141
5748
 
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
- });
5749
+ function handleSubmit(e) {
5750
+ e?.preventDefault();
5751
+ const value = cmdInput.value.trim();
5752
+ if (!value || !connected) return;
5753
+ cmdInput.value = '';
5754
+ term.io.writeUTF8('$ ' + value + '\r
5755
+ ');
5756
+ ws.send(JSON.stringify({ type: 'terminal:input', data: value + '\r' }));
5757
+ setTimeout(() => cmdInput.focus(), 50);
5758
+ return false;
5759
+ }
6157
5760
 
6158
- // Heartbeat
6159
- setInterval(function() {
6160
- if (ws && ws.readyState === WebSocket.OPEN) {
6161
- ws.send(JSON.stringify({ type: 'ping' }));
6162
- }
6163
- }, 25000);
5761
+ function send(cmd) {
5762
+ if (!connected) return;
5763
+ term.io.writeUTF8('$ ' + cmd + '\r
5764
+ ');
5765
+ ws.send(JSON.stringify({ type: 'terminal:input', data: cmd + '\r' }));
5766
+ }
6164
5767
 
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
- }
5768
+ cmdInput.addEventListener('keydown', (e) => {
5769
+ if (e.key === 'Enter' && !e.shiftKey) {
5770
+ e.preventDefault();
5771
+ handleSubmit();
6172
5772
  }
5773
+ });
6173
5774
 
6174
- window.addEventListener('resize', sendResize);
6175
- setTimeout(sendResize, 1000);
6176
-
6177
- // Start connection
6178
- if (token) {
6179
- connect();
6180
- } else {
6181
- authStatus.textContent = 'Invalid session URL';
6182
- authStatus.classList.add('error');
6183
- }
6184
- })();
5775
+ document.getElementById('terminal')?.addEventListener('click', () => {
5776
+ if (connected) cmdInput.focus();
5777
+ });
6185
5778
  </script>
6186
5779
  </body>
6187
5780
  </html>`;
@@ -6283,6 +5876,7 @@ var init_server = __esm({
6283
5876
  });
6284
5877
  this.terminal.on("data", (data) => {
6285
5878
  this.broadcast({ type: MessageTypes.TERMINAL_OUTPUT, payload: { data } });
5879
+ this.emit("terminal:output", data);
6286
5880
  });
6287
5881
  this.terminal.on("exit", (code) => {
6288
5882
  this.broadcast({ type: MessageTypes.TERMINAL_EXIT, payload: { code } });
@@ -6499,9 +6093,6 @@ var init_server = __esm({
6499
6093
  * Handle authentication
6500
6094
  */
6501
6095
  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
6096
  if (token === this.sessionSecret) {
6506
6097
  client.authenticated = true;
6507
6098
  if (this.session) {
@@ -6522,9 +6113,7 @@ var init_server = __esm({
6522
6113
  this.terminal?.start();
6523
6114
  }
6524
6115
  this.emit("client:connected", client.device);
6525
- console.log(`[Tunnel] Auth success for ${client.id}`);
6526
6116
  } else {
6527
- console.log(`[Tunnel] Auth failed for ${client.id}`);
6528
6117
  client.ws.send(JSON.stringify({ type: MessageTypes.AUTH_FAILED, timestamp: Date.now() }));
6529
6118
  setTimeout(() => client.ws.close(1008, "Authentication failed"), 100);
6530
6119
  }
@@ -6648,141 +6237,12 @@ __export(index_exports, {
6648
6237
  MessageTypes: () => MessageTypes,
6649
6238
  RemoteServer: () => RemoteServer,
6650
6239
  TerminalManager: () => TerminalManager,
6651
- TunnelManager: () => TunnelManager,
6652
- checkTunnelAvailability: () => checkTunnelAvailability,
6653
6240
  createRemoteServer: () => createRemoteServer,
6654
- findAvailableTunnel: () => findAvailableTunnel,
6655
- generateQR: () => generateQR,
6656
- generateQRDataURL: () => generateQRDataURL,
6657
- getWebClient: () => getWebClient,
6658
- progressBar: () => progressBar,
6659
- renderSessionCard: () => renderSessionCard
6241
+ getWebClient: () => getWebClient
6660
6242
  });
6661
6243
  module.exports = __toCommonJS(index_exports);
6662
6244
  init_server();
6663
6245
  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
6246
  init_web_client();
6787
6247
  init_types();
6788
6248
  async function createRemoteServer(config = {}) {
@@ -6797,13 +6257,6 @@ async function createRemoteServer(config = {}) {
6797
6257
  MessageTypes,
6798
6258
  RemoteServer,
6799
6259
  TerminalManager,
6800
- TunnelManager,
6801
- checkTunnelAvailability,
6802
6260
  createRemoteServer,
6803
- findAvailableTunnel,
6804
- generateQR,
6805
- generateQRDataURL,
6806
- getWebClient,
6807
- progressBar,
6808
- renderSessionCard
6261
+ getWebClient
6809
6262
  });