nikcli-remote 1.0.8 → 1.0.9
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-FYVPBMXV.js → chunk-3IFHAOGG.js} +588 -231
- package/dist/index.cjs +587 -230
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -2
- package/dist/{server-OYMSDTRP.js → server-MBJQBTJF.js} +1 -1
- package/package.json +1 -1
- package/src/web-client.ts +593 -236
|
@@ -1661,292 +1661,649 @@ function getWebClient() {
|
|
|
1661
1661
|
<html lang="en">
|
|
1662
1662
|
<head>
|
|
1663
1663
|
<meta charset="UTF-8">
|
|
1664
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no
|
|
1664
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
|
1665
1665
|
<meta name="apple-mobile-web-app-capable" content="yes">
|
|
1666
|
-
<meta name="
|
|
1666
|
+
<meta name="mobile-web-app-capable" content="yes">
|
|
1667
|
+
<meta name="theme-color" content="#0d1117">
|
|
1667
1668
|
<title>NikCLI Remote</title>
|
|
1668
|
-
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/xterm@5.3.0/css/xterm.css" />
|
|
1669
1669
|
<style>
|
|
1670
|
-
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
1671
1670
|
:root {
|
|
1672
|
-
--bg
|
|
1671
|
+
--bg: #0d1117;
|
|
1673
1672
|
--bg-secondary: #161b22;
|
|
1673
|
+
--fg: #e6edf3;
|
|
1674
|
+
--fg-muted: #8b949e;
|
|
1674
1675
|
--accent: #58a6ff;
|
|
1675
1676
|
--success: #3fb950;
|
|
1677
|
+
--warning: #d29922;
|
|
1678
|
+
--error: #f85149;
|
|
1676
1679
|
--border: #30363d;
|
|
1680
|
+
--font-mono: 'SF Mono', 'Fira Code', 'Consolas', monospace;
|
|
1677
1681
|
}
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1682
|
+
|
|
1683
|
+
* {
|
|
1684
|
+
box-sizing: border-box;
|
|
1685
|
+
margin: 0;
|
|
1686
|
+
padding: 0;
|
|
1687
|
+
-webkit-tap-highlight-color: transparent;
|
|
1688
|
+
}
|
|
1689
|
+
|
|
1690
|
+
html, body {
|
|
1691
|
+
height: 100%;
|
|
1692
|
+
background: var(--bg);
|
|
1693
|
+
color: var(--fg);
|
|
1694
|
+
font-family: var(--font-mono);
|
|
1695
|
+
font-size: 14px;
|
|
1696
|
+
overflow: hidden;
|
|
1697
|
+
touch-action: manipulation;
|
|
1698
|
+
}
|
|
1699
|
+
|
|
1700
|
+
#app {
|
|
1683
1701
|
display: flex;
|
|
1684
1702
|
flex-direction: column;
|
|
1703
|
+
height: 100%;
|
|
1704
|
+
height: 100dvh;
|
|
1685
1705
|
}
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1706
|
+
|
|
1707
|
+
/* Header */
|
|
1708
|
+
#header {
|
|
1709
|
+
display: flex;
|
|
1710
|
+
align-items: center;
|
|
1711
|
+
justify-content: space-between;
|
|
1690
1712
|
padding: 12px 16px;
|
|
1713
|
+
background: var(--bg-secondary);
|
|
1714
|
+
border-bottom: 1px solid var(--border);
|
|
1691
1715
|
flex-shrink: 0;
|
|
1692
|
-
padding-bottom: env(safe-area-inset-bottom, 12px);
|
|
1693
1716
|
}
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1717
|
+
|
|
1718
|
+
#header h1 {
|
|
1719
|
+
font-size: 16px;
|
|
1720
|
+
font-weight: 600;
|
|
1721
|
+
color: var(--accent);
|
|
1722
|
+
display: flex;
|
|
1723
|
+
align-items: center;
|
|
1724
|
+
gap: 8px;
|
|
1725
|
+
}
|
|
1726
|
+
|
|
1727
|
+
#header h1::before {
|
|
1728
|
+
content: '';
|
|
1729
|
+
width: 10px;
|
|
1730
|
+
height: 10px;
|
|
1731
|
+
background: var(--accent);
|
|
1732
|
+
border-radius: 2px;
|
|
1733
|
+
}
|
|
1734
|
+
|
|
1735
|
+
#status {
|
|
1736
|
+
display: flex;
|
|
1737
|
+
align-items: center;
|
|
1738
|
+
gap: 6px;
|
|
1739
|
+
font-size: 12px;
|
|
1740
|
+
color: var(--fg-muted);
|
|
1741
|
+
}
|
|
1742
|
+
|
|
1743
|
+
#status-dot {
|
|
1744
|
+
width: 8px;
|
|
1745
|
+
height: 8px;
|
|
1746
|
+
border-radius: 50%;
|
|
1747
|
+
background: var(--error);
|
|
1748
|
+
transition: background 0.3s;
|
|
1749
|
+
}
|
|
1750
|
+
|
|
1751
|
+
#status-dot.connected {
|
|
1752
|
+
background: var(--success);
|
|
1753
|
+
}
|
|
1754
|
+
|
|
1755
|
+
#status-dot.connecting {
|
|
1756
|
+
background: var(--warning);
|
|
1757
|
+
animation: pulse 1s infinite;
|
|
1758
|
+
}
|
|
1759
|
+
|
|
1760
|
+
@keyframes pulse {
|
|
1761
|
+
0%, 100% { opacity: 1; }
|
|
1762
|
+
50% { opacity: 0.5; }
|
|
1763
|
+
}
|
|
1764
|
+
|
|
1765
|
+
/* Terminal */
|
|
1766
|
+
#terminal-container {
|
|
1697
1767
|
flex: 1;
|
|
1698
|
-
|
|
1768
|
+
overflow: hidden;
|
|
1769
|
+
position: relative;
|
|
1770
|
+
}
|
|
1771
|
+
|
|
1772
|
+
#terminal {
|
|
1773
|
+
height: 100%;
|
|
1774
|
+
padding: 12px;
|
|
1775
|
+
overflow-y: auto;
|
|
1776
|
+
overflow-x: hidden;
|
|
1777
|
+
font-size: 13px;
|
|
1778
|
+
line-height: 1.5;
|
|
1779
|
+
white-space: pre-wrap;
|
|
1780
|
+
word-break: break-all;
|
|
1781
|
+
-webkit-overflow-scrolling: touch;
|
|
1782
|
+
}
|
|
1783
|
+
|
|
1784
|
+
#terminal::-webkit-scrollbar {
|
|
1785
|
+
width: 6px;
|
|
1786
|
+
}
|
|
1787
|
+
|
|
1788
|
+
#terminal::-webkit-scrollbar-track {
|
|
1789
|
+
background: var(--bg);
|
|
1790
|
+
}
|
|
1791
|
+
|
|
1792
|
+
#terminal::-webkit-scrollbar-thumb {
|
|
1793
|
+
background: var(--border);
|
|
1794
|
+
border-radius: 3px;
|
|
1795
|
+
}
|
|
1796
|
+
|
|
1797
|
+
.cursor {
|
|
1798
|
+
display: inline-block;
|
|
1799
|
+
width: 8px;
|
|
1800
|
+
height: 16px;
|
|
1801
|
+
background: var(--fg);
|
|
1802
|
+
animation: blink 1s step-end infinite;
|
|
1803
|
+
vertical-align: text-bottom;
|
|
1804
|
+
}
|
|
1805
|
+
|
|
1806
|
+
@keyframes blink {
|
|
1807
|
+
50% { opacity: 0; }
|
|
1808
|
+
}
|
|
1809
|
+
|
|
1810
|
+
/* Notifications */
|
|
1811
|
+
#notifications {
|
|
1812
|
+
position: fixed;
|
|
1813
|
+
top: 60px;
|
|
1814
|
+
left: 12px;
|
|
1815
|
+
right: 12px;
|
|
1816
|
+
z-index: 1000;
|
|
1817
|
+
pointer-events: none;
|
|
1818
|
+
}
|
|
1819
|
+
|
|
1820
|
+
.notification {
|
|
1821
|
+
background: var(--bg-secondary);
|
|
1699
1822
|
border: 1px solid var(--border);
|
|
1700
|
-
border-radius:
|
|
1823
|
+
border-radius: 8px;
|
|
1701
1824
|
padding: 12px 16px;
|
|
1702
|
-
|
|
1825
|
+
margin-bottom: 8px;
|
|
1826
|
+
animation: slideIn 0.3s ease;
|
|
1827
|
+
pointer-events: auto;
|
|
1828
|
+
box-shadow: 0 4px 12px rgba(0,0,0,0.4);
|
|
1829
|
+
}
|
|
1830
|
+
|
|
1831
|
+
.notification.success { border-left: 3px solid var(--success); }
|
|
1832
|
+
.notification.error { border-left: 3px solid var(--error); }
|
|
1833
|
+
.notification.warning { border-left: 3px solid var(--warning); }
|
|
1834
|
+
.notification.info { border-left: 3px solid var(--accent); }
|
|
1835
|
+
|
|
1836
|
+
.notification h4 {
|
|
1837
|
+
font-size: 14px;
|
|
1838
|
+
font-weight: 600;
|
|
1839
|
+
margin-bottom: 4px;
|
|
1840
|
+
}
|
|
1841
|
+
|
|
1842
|
+
.notification p {
|
|
1843
|
+
font-size: 12px;
|
|
1844
|
+
color: var(--fg-muted);
|
|
1845
|
+
}
|
|
1846
|
+
|
|
1847
|
+
@keyframes slideIn {
|
|
1848
|
+
from { transform: translateY(-20px); opacity: 0; }
|
|
1849
|
+
to { transform: translateY(0); opacity: 1; }
|
|
1850
|
+
}
|
|
1851
|
+
|
|
1852
|
+
/* Quick Keys */
|
|
1853
|
+
#quickkeys {
|
|
1854
|
+
display: grid;
|
|
1855
|
+
grid-template-columns: repeat(6, 1fr);
|
|
1856
|
+
gap: 6px;
|
|
1857
|
+
padding: 8px 12px;
|
|
1858
|
+
background: var(--bg-secondary);
|
|
1859
|
+
border-top: 1px solid var(--border);
|
|
1860
|
+
flex-shrink: 0;
|
|
1861
|
+
}
|
|
1862
|
+
|
|
1863
|
+
.qkey {
|
|
1864
|
+
background: var(--bg);
|
|
1865
|
+
border: 1px solid var(--border);
|
|
1866
|
+
border-radius: 6px;
|
|
1867
|
+
padding: 10px 4px;
|
|
1868
|
+
color: var(--fg);
|
|
1869
|
+
font-size: 11px;
|
|
1870
|
+
font-family: var(--font-mono);
|
|
1871
|
+
text-align: center;
|
|
1872
|
+
cursor: pointer;
|
|
1873
|
+
user-select: none;
|
|
1874
|
+
transition: background 0.1s, transform 0.1s;
|
|
1875
|
+
}
|
|
1876
|
+
|
|
1877
|
+
.qkey:active {
|
|
1878
|
+
background: var(--border);
|
|
1879
|
+
transform: scale(0.95);
|
|
1880
|
+
}
|
|
1881
|
+
|
|
1882
|
+
.qkey.wide {
|
|
1883
|
+
grid-column: span 2;
|
|
1884
|
+
}
|
|
1885
|
+
|
|
1886
|
+
.qkey.accent {
|
|
1887
|
+
background: var(--accent);
|
|
1888
|
+
border-color: var(--accent);
|
|
1889
|
+
color: #fff;
|
|
1890
|
+
}
|
|
1891
|
+
|
|
1892
|
+
/* Input */
|
|
1893
|
+
#input-container {
|
|
1894
|
+
padding: 12px;
|
|
1895
|
+
background: var(--bg-secondary);
|
|
1896
|
+
border-top: 1px solid var(--border);
|
|
1897
|
+
flex-shrink: 0;
|
|
1898
|
+
}
|
|
1899
|
+
|
|
1900
|
+
#input-row {
|
|
1901
|
+
display: flex;
|
|
1902
|
+
gap: 8px;
|
|
1903
|
+
}
|
|
1904
|
+
|
|
1905
|
+
#input {
|
|
1906
|
+
flex: 1;
|
|
1907
|
+
background: var(--bg);
|
|
1908
|
+
border: 1px solid var(--border);
|
|
1909
|
+
border-radius: 8px;
|
|
1910
|
+
padding: 12px 14px;
|
|
1911
|
+
color: var(--fg);
|
|
1912
|
+
font-family: var(--font-mono);
|
|
1703
1913
|
font-size: 16px;
|
|
1704
|
-
font-family: 'SF Mono', Monaco, monospace;
|
|
1705
1914
|
outline: none;
|
|
1706
|
-
|
|
1915
|
+
transition: border-color 0.2s;
|
|
1916
|
+
}
|
|
1917
|
+
|
|
1918
|
+
#input:focus {
|
|
1919
|
+
border-color: var(--accent);
|
|
1920
|
+
}
|
|
1921
|
+
|
|
1922
|
+
#input::placeholder {
|
|
1923
|
+
color: var(--fg-muted);
|
|
1707
1924
|
}
|
|
1708
|
-
|
|
1709
|
-
#send
|
|
1925
|
+
|
|
1926
|
+
#send {
|
|
1710
1927
|
background: var(--accent);
|
|
1711
|
-
color:
|
|
1928
|
+
color: #fff;
|
|
1712
1929
|
border: none;
|
|
1713
|
-
border-radius:
|
|
1714
|
-
padding: 12px
|
|
1715
|
-
font-size:
|
|
1930
|
+
border-radius: 8px;
|
|
1931
|
+
padding: 12px 20px;
|
|
1932
|
+
font-size: 14px;
|
|
1716
1933
|
font-weight: 600;
|
|
1934
|
+
font-family: var(--font-mono);
|
|
1717
1935
|
cursor: pointer;
|
|
1718
|
-
|
|
1936
|
+
transition: opacity 0.2s, transform 0.1s;
|
|
1719
1937
|
}
|
|
1720
|
-
|
|
1721
|
-
#
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1938
|
+
|
|
1939
|
+
#send:active {
|
|
1940
|
+
opacity: 0.8;
|
|
1941
|
+
transform: scale(0.98);
|
|
1942
|
+
}
|
|
1943
|
+
|
|
1944
|
+
/* Auth Screen */
|
|
1945
|
+
#auth-screen {
|
|
1946
|
+
position: fixed;
|
|
1947
|
+
inset: 0;
|
|
1948
|
+
background: var(--bg);
|
|
1725
1949
|
display: flex;
|
|
1726
|
-
|
|
1950
|
+
flex-direction: column;
|
|
1727
1951
|
align-items: center;
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
}
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
}
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
@
|
|
1761
|
-
#input-
|
|
1762
|
-
|
|
1952
|
+
justify-content: center;
|
|
1953
|
+
gap: 20px;
|
|
1954
|
+
z-index: 2000;
|
|
1955
|
+
}
|
|
1956
|
+
|
|
1957
|
+
#auth-screen.hidden {
|
|
1958
|
+
display: none;
|
|
1959
|
+
}
|
|
1960
|
+
|
|
1961
|
+
.spinner {
|
|
1962
|
+
width: 40px;
|
|
1963
|
+
height: 40px;
|
|
1964
|
+
border: 3px solid var(--border);
|
|
1965
|
+
border-top-color: var(--accent);
|
|
1966
|
+
border-radius: 50%;
|
|
1967
|
+
animation: spin 1s linear infinite;
|
|
1968
|
+
}
|
|
1969
|
+
|
|
1970
|
+
@keyframes spin {
|
|
1971
|
+
to { transform: rotate(360deg); }
|
|
1972
|
+
}
|
|
1973
|
+
|
|
1974
|
+
#auth-screen p {
|
|
1975
|
+
color: var(--fg-muted);
|
|
1976
|
+
font-size: 14px;
|
|
1977
|
+
}
|
|
1978
|
+
|
|
1979
|
+
#auth-screen .error {
|
|
1980
|
+
color: var(--error);
|
|
1981
|
+
}
|
|
1982
|
+
|
|
1983
|
+
/* Safe area padding for notched devices */
|
|
1984
|
+
@supports (padding: env(safe-area-inset-bottom)) {
|
|
1985
|
+
#input-container {
|
|
1986
|
+
padding-bottom: calc(12px + env(safe-area-inset-bottom));
|
|
1987
|
+
}
|
|
1988
|
+
}
|
|
1989
|
+
|
|
1990
|
+
/* Landscape adjustments */
|
|
1991
|
+
@media (max-height: 500px) {
|
|
1992
|
+
#quickkeys {
|
|
1993
|
+
grid-template-columns: repeat(12, 1fr);
|
|
1994
|
+
padding: 6px 8px;
|
|
1995
|
+
}
|
|
1996
|
+
.qkey {
|
|
1997
|
+
padding: 8px 2px;
|
|
1998
|
+
font-size: 10px;
|
|
1999
|
+
}
|
|
2000
|
+
#terminal {
|
|
2001
|
+
font-size: 12px;
|
|
2002
|
+
}
|
|
1763
2003
|
}
|
|
1764
2004
|
</style>
|
|
1765
2005
|
</head>
|
|
1766
2006
|
<body>
|
|
1767
|
-
<div id="
|
|
1768
|
-
<div
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
<div id="auth-text">Connecting...</div>
|
|
1772
|
-
</div>
|
|
1773
|
-
<div class="quick-btns">
|
|
1774
|
-
<button class="quick-btn" onclick="send('help')">/help</button>
|
|
1775
|
-
<button class="quick-btn" onclick="send('ls -la')">ls -la</button>
|
|
1776
|
-
<button class="quick-btn" onclick="send('pwd')">pwd</button>
|
|
1777
|
-
<button class="quick-btn" onclick="send('whoami')">whoami</button>
|
|
1778
|
-
<button class="quick-btn" onclick="send('clear')">clear</button>
|
|
2007
|
+
<div id="app">
|
|
2008
|
+
<div id="auth-screen">
|
|
2009
|
+
<div class="spinner"></div>
|
|
2010
|
+
<p id="auth-status">Connecting to NikCLI...</p>
|
|
1779
2011
|
</div>
|
|
1780
|
-
<div class="hint">Mobile keyboard to type commands</div>
|
|
1781
|
-
</div>
|
|
1782
2012
|
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
<
|
|
1786
|
-
|
|
2013
|
+
<header id="header">
|
|
2014
|
+
<h1>NikCLI Remote</h1>
|
|
2015
|
+
<div id="status">
|
|
2016
|
+
<span id="status-dot" class="connecting"></span>
|
|
2017
|
+
<span id="status-text">Connecting</span>
|
|
2018
|
+
</div>
|
|
2019
|
+
</header>
|
|
2020
|
+
|
|
2021
|
+
<div id="terminal-container">
|
|
2022
|
+
<div id="terminal"></div>
|
|
1787
2023
|
</div>
|
|
1788
|
-
<span id="session-id" style="color: #8b949e;"></span>
|
|
1789
|
-
</div>
|
|
1790
2024
|
|
|
1791
|
-
|
|
2025
|
+
<div id="notifications"></div>
|
|
2026
|
+
|
|
2027
|
+
<div id="quickkeys">
|
|
2028
|
+
<button class="qkey" data-key="\\t">Tab</button>
|
|
2029
|
+
<button class="qkey" data-key="\\x1b[A">\u2191</button>
|
|
2030
|
+
<button class="qkey" data-key="\\x1b[B">\u2193</button>
|
|
2031
|
+
<button class="qkey" data-key="\\x1b[D">\u2190</button>
|
|
2032
|
+
<button class="qkey" data-key="\\x1b[C">\u2192</button>
|
|
2033
|
+
<button class="qkey" data-key="\\x1b">Esc</button>
|
|
2034
|
+
<button class="qkey" data-key="\\x03">^C</button>
|
|
2035
|
+
<button class="qkey" data-key="\\x04">^D</button>
|
|
2036
|
+
<button class="qkey" data-key="\\x1a">^Z</button>
|
|
2037
|
+
<button class="qkey" data-key="\\x0c">^L</button>
|
|
2038
|
+
<button class="qkey wide accent" data-key="\\r">Enter \u23CE</button>
|
|
2039
|
+
</div>
|
|
1792
2040
|
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
</
|
|
2041
|
+
<div id="input-container">
|
|
2042
|
+
<div id="input-row">
|
|
2043
|
+
<input type="text" id="input" placeholder="Type command..." autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false">
|
|
2044
|
+
<button id="send">Send</button>
|
|
2045
|
+
</div>
|
|
2046
|
+
</div>
|
|
1799
2047
|
</div>
|
|
1800
2048
|
|
|
1801
|
-
<script src="https://cdn.jsdelivr.net/npm/xterm@5.3.0/lib/xterm.js"></script>
|
|
1802
|
-
<script src="https://cdn.jsdelivr.net/npm/xterm-addon-fit@0.8.0/lib/xterm-addon-fit.js"></script>
|
|
1803
2049
|
<script>
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
const sessionId = new URLSearchParams(location.search).get('s') || '';
|
|
1807
|
-
|
|
1808
|
-
const authOverlay = document.getElementById('auth-overlay');
|
|
1809
|
-
const authMsg = document.getElementById('auth-msg');
|
|
1810
|
-
const authText = document.getElementById('auth-text');
|
|
1811
|
-
const statusDot = document.getElementById('status-dot');
|
|
1812
|
-
const statusText = document.getElementById('status-text');
|
|
1813
|
-
const sessionSpan = document.getElementById('session-id');
|
|
1814
|
-
const cmdInput = document.getElementById('cmd-input');
|
|
1815
|
-
|
|
1816
|
-
// Initialize xterm.js
|
|
1817
|
-
term = new Terminal({
|
|
1818
|
-
cursorBlink: true,
|
|
1819
|
-
fontSize: 14,
|
|
1820
|
-
fontFamily: '"SF Mono", Monaco, Consolas, monospace',
|
|
1821
|
-
theme: {
|
|
1822
|
-
background: '#0d1117',
|
|
1823
|
-
foreground: '#e6edf3',
|
|
1824
|
-
cursor: '#3fb950',
|
|
1825
|
-
selectionBackground: '#264f78',
|
|
1826
|
-
black: '#484f58',
|
|
1827
|
-
red: '#f85149',
|
|
1828
|
-
green: '#3fb950',
|
|
1829
|
-
yellow: '#d29922',
|
|
1830
|
-
blue: '#58a6ff',
|
|
1831
|
-
magenta: '#a371f7',
|
|
1832
|
-
cyan: '#39c5cf',
|
|
1833
|
-
white: '#e6edf3',
|
|
1834
|
-
brightBlack: '#6e7681',
|
|
1835
|
-
brightRed: '#ffa198',
|
|
1836
|
-
brightGreen: '#7ee787',
|
|
1837
|
-
brightYellow: '#f0883e',
|
|
1838
|
-
brightBlue: '#79c0ff',
|
|
1839
|
-
brightMagenta: '#d2a8ff',
|
|
1840
|
-
brightCyan: '#56d4db',
|
|
1841
|
-
brightWhite: '#f0f6fc'
|
|
1842
|
-
},
|
|
1843
|
-
convertEol: true
|
|
1844
|
-
});
|
|
2050
|
+
(function() {
|
|
2051
|
+
'use strict';
|
|
1845
2052
|
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
term.writeln('Initializing NikCLI Remote...');
|
|
1851
|
-
term.writeln('Type commands below');
|
|
1852
|
-
|
|
1853
|
-
// Resize handler
|
|
1854
|
-
window.addEventListener('resize', () => {
|
|
1855
|
-
clearTimeout(window.resizeTimer);
|
|
1856
|
-
window.resizeTimer = setTimeout(() => fitAddon.fit(), 100);
|
|
1857
|
-
});
|
|
2053
|
+
// Parse URL params
|
|
2054
|
+
const params = new URLSearchParams(location.search);
|
|
2055
|
+
const token = params.get('t');
|
|
2056
|
+
const sessionId = params.get('s');
|
|
1858
2057
|
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
2058
|
+
// DOM elements
|
|
2059
|
+
const terminal = document.getElementById('terminal');
|
|
2060
|
+
const input = document.getElementById('input');
|
|
2061
|
+
const sendBtn = document.getElementById('send');
|
|
2062
|
+
const statusDot = document.getElementById('status-dot');
|
|
2063
|
+
const statusText = document.getElementById('status-text');
|
|
2064
|
+
const authScreen = document.getElementById('auth-screen');
|
|
2065
|
+
const authStatus = document.getElementById('auth-status');
|
|
2066
|
+
const notifications = document.getElementById('notifications');
|
|
1866
2067
|
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
2068
|
+
// State
|
|
2069
|
+
let ws = null;
|
|
2070
|
+
let reconnectAttempts = 0;
|
|
2071
|
+
const maxReconnectAttempts = 5;
|
|
2072
|
+
let terminalEnabled = true;
|
|
1870
2073
|
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
};
|
|
2074
|
+
// Connect to WebSocket
|
|
2075
|
+
function connect() {
|
|
2076
|
+
const protocol = location.protocol === 'https:' ? 'wss:' : 'ws:';
|
|
2077
|
+
ws = new WebSocket(protocol + '//' + location.host);
|
|
1876
2078
|
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
2079
|
+
ws.onopen = function() {
|
|
2080
|
+
setStatus('connecting', 'Authenticating...');
|
|
2081
|
+
ws.send(JSON.stringify({ type: 'auth', token: token }));
|
|
2082
|
+
};
|
|
1880
2083
|
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
2084
|
+
ws.onmessage = function(event) {
|
|
2085
|
+
try {
|
|
2086
|
+
const msg = JSON.parse(event.data);
|
|
2087
|
+
handleMessage(msg);
|
|
2088
|
+
} catch (e) {
|
|
2089
|
+
console.error('Parse error:', e);
|
|
2090
|
+
}
|
|
2091
|
+
};
|
|
2092
|
+
|
|
2093
|
+
ws.onclose = function() {
|
|
2094
|
+
setStatus('disconnected', 'Disconnected');
|
|
2095
|
+
if (reconnectAttempts < maxReconnectAttempts) {
|
|
2096
|
+
reconnectAttempts++;
|
|
2097
|
+
const delay = Math.min(2000 * reconnectAttempts, 10000);
|
|
2098
|
+
setTimeout(connect, delay);
|
|
2099
|
+
} else {
|
|
2100
|
+
authStatus.textContent = 'Connection failed. Refresh to retry.';
|
|
2101
|
+
authStatus.classList.add('error');
|
|
2102
|
+
authScreen.classList.remove('hidden');
|
|
2103
|
+
}
|
|
2104
|
+
};
|
|
1887
2105
|
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
function handleMessage(msg) {
|
|
1892
|
-
switch (msg.type) {
|
|
1893
|
-
case 'auth:required':
|
|
1894
|
-
ws.send(JSON.stringify({ type: 'auth', token }));
|
|
1895
|
-
break;
|
|
1896
|
-
case 'auth:success':
|
|
1897
|
-
connected = true;
|
|
1898
|
-
authOverlay.classList.add('hidden');
|
|
1899
|
-
setStatus('connected', 'Connected');
|
|
1900
|
-
sessionSpan.textContent = sessionId ? 'Session: ' + sessionId : '';
|
|
1901
|
-
term.writeln('\u2713 Connected to NikCLI');
|
|
1902
|
-
break;
|
|
1903
|
-
case 'auth:failed':
|
|
1904
|
-
authMsg.classList.add('error');
|
|
1905
|
-
authText.textContent = 'Authentication failed';
|
|
1906
|
-
break;
|
|
1907
|
-
case 'terminal:output':
|
|
1908
|
-
if (msg.payload?.data) term.write(msg.payload.data);
|
|
1909
|
-
break;
|
|
1910
|
-
case 'terminal:exit':
|
|
1911
|
-
term.writeln('Process exited: ' + (msg.payload?.code || 0) + '');
|
|
1912
|
-
break;
|
|
2106
|
+
ws.onerror = function() {
|
|
2107
|
+
console.error('WebSocket error');
|
|
2108
|
+
};
|
|
1913
2109
|
}
|
|
1914
|
-
}
|
|
1915
2110
|
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
2111
|
+
// Handle incoming message
|
|
2112
|
+
function handleMessage(msg) {
|
|
2113
|
+
switch (msg.type) {
|
|
2114
|
+
case 'auth:required':
|
|
2115
|
+
// Already sent auth on open
|
|
2116
|
+
break;
|
|
1920
2117
|
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
}
|
|
2118
|
+
case 'auth:success':
|
|
2119
|
+
authScreen.classList.add('hidden');
|
|
2120
|
+
setStatus('connected', 'Connected');
|
|
2121
|
+
reconnectAttempts = 0;
|
|
2122
|
+
terminalEnabled = msg.payload?.terminalEnabled !== false;
|
|
2123
|
+
if (terminalEnabled) {
|
|
2124
|
+
appendOutput('\\x1b[32mConnected to NikCLI\\x1b[0m\\n\\n');
|
|
2125
|
+
}
|
|
2126
|
+
break;
|
|
1931
2127
|
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
2128
|
+
case 'auth:failed':
|
|
2129
|
+
authStatus.textContent = 'Authentication failed';
|
|
2130
|
+
authStatus.classList.add('error');
|
|
2131
|
+
break;
|
|
2132
|
+
|
|
2133
|
+
case 'terminal:output':
|
|
2134
|
+
if (msg.payload?.data) {
|
|
2135
|
+
appendOutput(msg.payload.data);
|
|
2136
|
+
}
|
|
2137
|
+
break;
|
|
2138
|
+
|
|
2139
|
+
case 'terminal:exit':
|
|
2140
|
+
appendOutput('\\n\\x1b[33m[Process exited with code ' + (msg.payload?.code || 0) + ']\\x1b[0m\\n');
|
|
2141
|
+
break;
|
|
2142
|
+
|
|
2143
|
+
case 'notification':
|
|
2144
|
+
showNotification(msg.payload);
|
|
2145
|
+
break;
|
|
2146
|
+
|
|
2147
|
+
case 'session:end':
|
|
2148
|
+
appendOutput('\\n\\x1b[31m[Session ended]\\x1b[0m\\n');
|
|
2149
|
+
setStatus('disconnected', 'Session ended');
|
|
2150
|
+
break;
|
|
1937
2151
|
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
2152
|
+
case 'pong':
|
|
2153
|
+
// Heartbeat response
|
|
2154
|
+
break;
|
|
2155
|
+
|
|
2156
|
+
default:
|
|
2157
|
+
console.log('Unknown message:', msg.type);
|
|
2158
|
+
}
|
|
1942
2159
|
}
|
|
1943
|
-
});
|
|
1944
2160
|
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
2161
|
+
// Append text to terminal with ANSI support
|
|
2162
|
+
function appendOutput(text) {
|
|
2163
|
+
// Simple ANSI to HTML conversion
|
|
2164
|
+
const html = ansiToHtml(text);
|
|
2165
|
+
terminal.innerHTML += html;
|
|
2166
|
+
terminal.scrollTop = terminal.scrollHeight;
|
|
2167
|
+
}
|
|
2168
|
+
|
|
2169
|
+
// Basic ANSI to HTML
|
|
2170
|
+
function ansiToHtml(text) {
|
|
2171
|
+
const ansiColors = {
|
|
2172
|
+
'30': '#6e7681', '31': '#f85149', '32': '#3fb950', '33': '#d29922',
|
|
2173
|
+
'34': '#58a6ff', '35': '#bc8cff', '36': '#76e3ea', '37': '#e6edf3',
|
|
2174
|
+
'90': '#6e7681', '91': '#f85149', '92': '#3fb950', '93': '#d29922',
|
|
2175
|
+
'94': '#58a6ff', '95': '#bc8cff', '96': '#76e3ea', '97': '#ffffff'
|
|
2176
|
+
};
|
|
2177
|
+
|
|
2178
|
+
let result = '';
|
|
2179
|
+
let currentStyle = '';
|
|
2180
|
+
|
|
2181
|
+
const parts = text.split(/\\x1b\\[([0-9;]+)m/);
|
|
2182
|
+
for (let i = 0; i < parts.length; i++) {
|
|
2183
|
+
if (i % 2 === 0) {
|
|
2184
|
+
// Text content
|
|
2185
|
+
result += escapeHtml(parts[i]);
|
|
2186
|
+
} else {
|
|
2187
|
+
// ANSI code
|
|
2188
|
+
const codes = parts[i].split(';');
|
|
2189
|
+
for (const code of codes) {
|
|
2190
|
+
if (code === '0') {
|
|
2191
|
+
if (currentStyle) {
|
|
2192
|
+
result += '</span>';
|
|
2193
|
+
currentStyle = '';
|
|
2194
|
+
}
|
|
2195
|
+
} else if (code === '1') {
|
|
2196
|
+
currentStyle = 'font-weight:bold;';
|
|
2197
|
+
result += '<span style="' + currentStyle + '">';
|
|
2198
|
+
} else if (ansiColors[code]) {
|
|
2199
|
+
if (currentStyle) result += '</span>';
|
|
2200
|
+
currentStyle = 'color:' + ansiColors[code] + ';';
|
|
2201
|
+
result += '<span style="' + currentStyle + '">';
|
|
2202
|
+
}
|
|
2203
|
+
}
|
|
2204
|
+
}
|
|
2205
|
+
}
|
|
2206
|
+
|
|
2207
|
+
if (currentStyle) result += '</span>';
|
|
2208
|
+
return result;
|
|
2209
|
+
}
|
|
2210
|
+
|
|
2211
|
+
// Escape HTML
|
|
2212
|
+
function escapeHtml(text) {
|
|
2213
|
+
return text
|
|
2214
|
+
.replace(/&/g, '&')
|
|
2215
|
+
.replace(/</g, '<')
|
|
2216
|
+
.replace(/>/g, '>')
|
|
2217
|
+
.replace(/"/g, '"')
|
|
2218
|
+
.replace(/\\n/g, '<br>')
|
|
2219
|
+
.replace(/ /g, ' ');
|
|
2220
|
+
}
|
|
2221
|
+
|
|
2222
|
+
// Set connection status
|
|
2223
|
+
function setStatus(state, text) {
|
|
2224
|
+
statusDot.className = state === 'connected' ? 'connected' :
|
|
2225
|
+
state === 'connecting' ? 'connecting' : '';
|
|
2226
|
+
statusText.textContent = text;
|
|
2227
|
+
}
|
|
1948
2228
|
|
|
1949
|
-
|
|
2229
|
+
// Send data to terminal
|
|
2230
|
+
function send(data) {
|
|
2231
|
+
if (ws && ws.readyState === WebSocket.OPEN) {
|
|
2232
|
+
ws.send(JSON.stringify({ type: 'terminal:input', data: data }));
|
|
2233
|
+
}
|
|
2234
|
+
}
|
|
2235
|
+
|
|
2236
|
+
// Show notification
|
|
2237
|
+
function showNotification(n) {
|
|
2238
|
+
if (!n) return;
|
|
2239
|
+
const el = document.createElement('div');
|
|
2240
|
+
el.className = 'notification ' + (n.type || 'info');
|
|
2241
|
+
el.innerHTML = '<h4>' + escapeHtml(n.title || 'Notification') + '</h4>' +
|
|
2242
|
+
'<p>' + escapeHtml(n.body || '') + '</p>';
|
|
2243
|
+
notifications.appendChild(el);
|
|
2244
|
+
setTimeout(function() { el.remove(); }, 5000);
|
|
2245
|
+
}
|
|
2246
|
+
|
|
2247
|
+
// Event: Send button
|
|
2248
|
+
sendBtn.onclick = function() {
|
|
2249
|
+
if (input.value) {
|
|
2250
|
+
send(input.value + '\\r');
|
|
2251
|
+
input.value = '';
|
|
2252
|
+
}
|
|
2253
|
+
input.focus();
|
|
2254
|
+
};
|
|
2255
|
+
|
|
2256
|
+
// Event: Enter key in input
|
|
2257
|
+
input.onkeydown = function(e) {
|
|
2258
|
+
if (e.key === 'Enter') {
|
|
2259
|
+
e.preventDefault();
|
|
2260
|
+
sendBtn.click();
|
|
2261
|
+
}
|
|
2262
|
+
};
|
|
2263
|
+
|
|
2264
|
+
// Event: Quick keys
|
|
2265
|
+
document.querySelectorAll('.qkey').forEach(function(btn) {
|
|
2266
|
+
btn.onclick = function() {
|
|
2267
|
+
const key = btn.dataset.key;
|
|
2268
|
+
const decoded = key
|
|
2269
|
+
.replace(/\\\\x([0-9a-f]{2})/gi, function(_, hex) {
|
|
2270
|
+
return String.fromCharCode(parseInt(hex, 16));
|
|
2271
|
+
})
|
|
2272
|
+
.replace(/\\\\t/g, '\\t')
|
|
2273
|
+
.replace(/\\\\r/g, '\\r')
|
|
2274
|
+
.replace(/\\\\n/g, '\\n');
|
|
2275
|
+
send(decoded);
|
|
2276
|
+
input.focus();
|
|
2277
|
+
};
|
|
2278
|
+
});
|
|
2279
|
+
|
|
2280
|
+
// Heartbeat
|
|
2281
|
+
setInterval(function() {
|
|
2282
|
+
if (ws && ws.readyState === WebSocket.OPEN) {
|
|
2283
|
+
ws.send(JSON.stringify({ type: 'ping' }));
|
|
2284
|
+
}
|
|
2285
|
+
}, 25000);
|
|
2286
|
+
|
|
2287
|
+
// Handle resize
|
|
2288
|
+
function sendResize() {
|
|
2289
|
+
if (ws && ws.readyState === WebSocket.OPEN) {
|
|
2290
|
+
const cols = Math.floor(terminal.clientWidth / 8);
|
|
2291
|
+
const rows = Math.floor(terminal.clientHeight / 18);
|
|
2292
|
+
ws.send(JSON.stringify({ type: 'terminal:resize', cols: cols, rows: rows }));
|
|
2293
|
+
}
|
|
2294
|
+
}
|
|
2295
|
+
|
|
2296
|
+
window.addEventListener('resize', sendResize);
|
|
2297
|
+
setTimeout(sendResize, 1000);
|
|
2298
|
+
|
|
2299
|
+
// Start connection
|
|
2300
|
+
if (token) {
|
|
2301
|
+
connect();
|
|
2302
|
+
} else {
|
|
2303
|
+
authStatus.textContent = 'Invalid session URL';
|
|
2304
|
+
authStatus.classList.add('error');
|
|
2305
|
+
}
|
|
2306
|
+
})();
|
|
1950
2307
|
</script>
|
|
1951
2308
|
</body>
|
|
1952
2309
|
</html>`;
|