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/{chunk-QLGPGAPC.js → chunk-ONRUR3Z7.js} +243 -637
- package/dist/index.cjs +244 -771
- package/dist/index.d.cts +3 -78
- package/dist/index.d.ts +3 -78
- package/dist/index.js +4 -135
- package/dist/{server-ISK4MDQQ.js → server-MURDBK6L.js} +1 -1
- package/package.json +1 -1
- package/src/index.ts +4 -61
- package/src/server.ts +3 -6
- package/src/tunnel.ts +11 -7
- package/src/web-client.ts +236 -593
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", (
|
|
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", (
|
|
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-
|
|
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
|
-
|
|
5563
|
-
|
|
5564
|
-
|
|
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
|
-
|
|
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
|
-
|
|
5779
|
-
|
|
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:
|
|
5540
|
+
background: #21262d;
|
|
5786
5541
|
border: 1px solid var(--border);
|
|
5787
|
-
border-radius:
|
|
5788
|
-
padding: 12px
|
|
5789
|
-
color:
|
|
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
|
-
|
|
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:
|
|
5553
|
+
color: white;
|
|
5807
5554
|
border: none;
|
|
5808
|
-
border-radius:
|
|
5809
|
-
padding: 12px
|
|
5810
|
-
font-size:
|
|
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
|
-
|
|
5815
|
-
}
|
|
5816
|
-
|
|
5817
|
-
#send:active {
|
|
5818
|
-
opacity: 0.8;
|
|
5819
|
-
transform: scale(0.98);
|
|
5560
|
+
-webkit-tap-highlight-color: transparent;
|
|
5820
5561
|
}
|
|
5821
|
-
|
|
5822
|
-
|
|
5823
|
-
|
|
5824
|
-
|
|
5825
|
-
|
|
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
|
-
|
|
5568
|
+
justify-content: space-between;
|
|
5829
5569
|
align-items: center;
|
|
5830
|
-
|
|
5831
|
-
|
|
5832
|
-
|
|
5833
|
-
}
|
|
5834
|
-
|
|
5835
|
-
|
|
5836
|
-
|
|
5837
|
-
}
|
|
5838
|
-
|
|
5839
|
-
|
|
5840
|
-
|
|
5841
|
-
|
|
5842
|
-
|
|
5843
|
-
|
|
5844
|
-
|
|
5845
|
-
|
|
5846
|
-
}
|
|
5847
|
-
|
|
5848
|
-
|
|
5849
|
-
|
|
5850
|
-
}
|
|
5851
|
-
|
|
5852
|
-
|
|
5853
|
-
|
|
5854
|
-
|
|
5855
|
-
|
|
5856
|
-
|
|
5857
|
-
|
|
5858
|
-
|
|
5859
|
-
}
|
|
5860
|
-
|
|
5861
|
-
|
|
5862
|
-
@
|
|
5863
|
-
#input-
|
|
5864
|
-
|
|
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="
|
|
5886
|
-
<div
|
|
5887
|
-
|
|
5888
|
-
|
|
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
|
-
|
|
5892
|
-
<
|
|
5893
|
-
<
|
|
5894
|
-
|
|
5895
|
-
|
|
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
|
-
|
|
5920
|
-
|
|
5921
|
-
|
|
5922
|
-
|
|
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
|
-
<
|
|
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
|
-
|
|
6035
|
-
|
|
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
|
-
|
|
6040
|
-
|
|
6041
|
-
|
|
6042
|
-
|
|
6043
|
-
|
|
6044
|
-
|
|
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
|
-
|
|
6048
|
-
|
|
6049
|
-
|
|
6050
|
-
|
|
6051
|
-
|
|
6052
|
-
|
|
6053
|
-
|
|
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
|
-
|
|
6057
|
-
|
|
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
|
-
|
|
6060
|
-
|
|
6061
|
-
|
|
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
|
-
|
|
6086
|
-
|
|
6087
|
-
|
|
5713
|
+
ws.onopen = () => {
|
|
5714
|
+
setStatus('connecting', 'Authenticating...');
|
|
5715
|
+
ws.send(JSON.stringify({ type: 'auth', token }));
|
|
5716
|
+
reconnectAttempts = 0;
|
|
5717
|
+
};
|
|
6088
5718
|
|
|
6089
|
-
|
|
6090
|
-
|
|
6091
|
-
|
|
6092
|
-
.replace(/&/g, '&')
|
|
6093
|
-
.replace(/</g, '<')
|
|
6094
|
-
.replace(/>/g, '>')
|
|
6095
|
-
.replace(/"/g, '"')
|
|
6096
|
-
.replace(/\\n/g, '<br>')
|
|
6097
|
-
.replace(/ /g, ' ');
|
|
6098
|
-
}
|
|
5719
|
+
ws.onmessage = (e) => {
|
|
5720
|
+
try { handleMessage(JSON.parse(e.data)); } catch (err) { console.error(err); }
|
|
5721
|
+
};
|
|
6099
5722
|
|
|
6100
|
-
|
|
6101
|
-
|
|
6102
|
-
|
|
6103
|
-
|
|
6104
|
-
|
|
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
|
-
|
|
6108
|
-
|
|
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
|
-
|
|
6115
|
-
|
|
6116
|
-
|
|
6117
|
-
|
|
6118
|
-
|
|
6119
|
-
|
|
6120
|
-
|
|
6121
|
-
|
|
6122
|
-
|
|
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
|
-
|
|
6126
|
-
|
|
6127
|
-
|
|
6128
|
-
|
|
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
|
-
|
|
6143
|
-
|
|
6144
|
-
|
|
6145
|
-
|
|
6146
|
-
|
|
6147
|
-
|
|
6148
|
-
|
|
6149
|
-
|
|
6150
|
-
|
|
6151
|
-
|
|
6152
|
-
|
|
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
|
-
|
|
6159
|
-
|
|
6160
|
-
|
|
6161
|
-
|
|
6162
|
-
|
|
6163
|
-
|
|
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
|
-
|
|
6166
|
-
|
|
6167
|
-
|
|
6168
|
-
|
|
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
|
-
|
|
6175
|
-
|
|
5793
|
+
document.getElementById('terminal')?.addEventListener('click', () => {
|
|
5794
|
+
if (connected) cmdInput.focus();
|
|
5795
|
+
});
|
|
6176
5796
|
|
|
6177
|
-
|
|
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
|
-
|
|
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
|
-
|
|
6804
|
-
generateQR,
|
|
6805
|
-
generateQRDataURL,
|
|
6806
|
-
getWebClient,
|
|
6807
|
-
progressBar,
|
|
6808
|
-
renderSessionCard
|
|
6281
|
+
getWebClient
|
|
6809
6282
|
});
|