lunel-cli 0.1.28 → 0.1.30
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.js +289 -132
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -11,7 +11,8 @@ import * as os from "os";
|
|
|
11
11
|
import { spawn, execSync } from "child_process";
|
|
12
12
|
import { createServer, createConnection } from "net";
|
|
13
13
|
import { createInterface } from "readline";
|
|
14
|
-
const
|
|
14
|
+
const DEFAULT_PROXY_URL = process.env.LUNEL_PROXY_URL || "https://gateway.lunel.dev";
|
|
15
|
+
const MANAGER_URL = process.env.LUNEL_MANAGER_URL || "https://manager.lunel.dev";
|
|
15
16
|
import { createRequire } from "module";
|
|
16
17
|
const __require = createRequire(import.meta.url);
|
|
17
18
|
const VERSION = __require("../package.json").version;
|
|
@@ -30,6 +31,13 @@ let lastCpuInfo = null;
|
|
|
30
31
|
let opencodeClient = null;
|
|
31
32
|
// Proxy tunnel management
|
|
32
33
|
let currentSessionCode = null;
|
|
34
|
+
let currentSessionPassword = null;
|
|
35
|
+
let currentPrimaryGateway = DEFAULT_PROXY_URL;
|
|
36
|
+
let currentBackupGateway = null;
|
|
37
|
+
let activeGatewayUrl = DEFAULT_PROXY_URL;
|
|
38
|
+
let shuttingDown = false;
|
|
39
|
+
let activeControlWs = null;
|
|
40
|
+
let activeDataWs = null;
|
|
33
41
|
const activeTunnels = new Map();
|
|
34
42
|
// Popular development server ports to scan on connect
|
|
35
43
|
const DEV_PORTS = [
|
|
@@ -1433,7 +1441,7 @@ async function handleProxyConnect(payload) {
|
|
|
1433
1441
|
throw Object.assign(new Error("tunnelId is required"), { code: "EINVAL" });
|
|
1434
1442
|
if (!port)
|
|
1435
1443
|
throw Object.assign(new Error("port is required"), { code: "EINVAL" });
|
|
1436
|
-
if (!currentSessionCode)
|
|
1444
|
+
if (!currentSessionCode && !currentSessionPassword)
|
|
1437
1445
|
throw Object.assign(new Error("no active session"), { code: "ENOENT" });
|
|
1438
1446
|
// 1. Open TCP connection to the local service
|
|
1439
1447
|
const tcpSocket = createConnection({ port, host: "127.0.0.1" });
|
|
@@ -1452,8 +1460,11 @@ async function handleProxyConnect(payload) {
|
|
|
1452
1460
|
});
|
|
1453
1461
|
});
|
|
1454
1462
|
// 2. Open proxy WebSocket to gateway
|
|
1455
|
-
const wsBase =
|
|
1456
|
-
const
|
|
1463
|
+
const wsBase = activeGatewayUrl.replace(/^http/, "ws");
|
|
1464
|
+
const authQuery = currentSessionPassword
|
|
1465
|
+
? `password=${encodeURIComponent(currentSessionPassword)}`
|
|
1466
|
+
: `code=${encodeURIComponent(currentSessionCode)}`;
|
|
1467
|
+
const proxyWsUrl = `${wsBase}/v1/ws/proxy?${authQuery}&tunnelId=${encodeURIComponent(tunnelId)}&role=cli`;
|
|
1457
1468
|
const proxyWs = new WebSocket(proxyWsUrl);
|
|
1458
1469
|
await new Promise((resolve, reject) => {
|
|
1459
1470
|
const timeout = setTimeout(() => {
|
|
@@ -1539,6 +1550,19 @@ async function processMessage(message) {
|
|
|
1539
1550
|
error: { code: "EPROTO", message: `Unsupported protocol version: ${v}` },
|
|
1540
1551
|
};
|
|
1541
1552
|
}
|
|
1553
|
+
// Validate required fields
|
|
1554
|
+
if (!ns || !action) {
|
|
1555
|
+
console.warn("[router] Ignoring message with missing ns/action:", JSON.stringify(message).substring(0, 300));
|
|
1556
|
+
return {
|
|
1557
|
+
v: 1,
|
|
1558
|
+
id,
|
|
1559
|
+
ns,
|
|
1560
|
+
action,
|
|
1561
|
+
ok: false,
|
|
1562
|
+
payload: {},
|
|
1563
|
+
error: { code: "EINVAL", message: `Missing namespace or action` },
|
|
1564
|
+
};
|
|
1565
|
+
}
|
|
1542
1566
|
try {
|
|
1543
1567
|
let result;
|
|
1544
1568
|
switch (ns) {
|
|
@@ -1795,11 +1819,35 @@ async function processMessage(message) {
|
|
|
1795
1819
|
};
|
|
1796
1820
|
}
|
|
1797
1821
|
}
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1822
|
+
function normalizeGatewayUrl(input) {
|
|
1823
|
+
if (/^https?:\/\//.test(input))
|
|
1824
|
+
return input.replace(/\/+$/, "");
|
|
1825
|
+
return `https://${input}`.replace(/\/+$/, "");
|
|
1826
|
+
}
|
|
1827
|
+
async function discoverGateways() {
|
|
1828
|
+
try {
|
|
1829
|
+
const response = await fetch(`${MANAGER_URL}/v1/gateways`, {
|
|
1830
|
+
method: "GET",
|
|
1831
|
+
headers: { "Content-Type": "application/json" },
|
|
1832
|
+
});
|
|
1833
|
+
if (!response.ok) {
|
|
1834
|
+
throw new Error(`manager returned ${response.status}`);
|
|
1835
|
+
}
|
|
1836
|
+
const data = (await response.json());
|
|
1837
|
+
const gateways = (data.gateways || []).map((x) => normalizeGatewayUrl(x)).filter(Boolean);
|
|
1838
|
+
const primary = data.primary ? normalizeGatewayUrl(data.primary) : gateways[0];
|
|
1839
|
+
const backup = data.backup ? normalizeGatewayUrl(data.backup) : gateways[1] || null;
|
|
1840
|
+
if (!primary)
|
|
1841
|
+
throw new Error("manager returned no healthy gateways");
|
|
1842
|
+
return { primary, backup };
|
|
1843
|
+
}
|
|
1844
|
+
catch (err) {
|
|
1845
|
+
console.warn(`[manager] gateway discovery failed, using default: ${err.message}`);
|
|
1846
|
+
return { primary: DEFAULT_PROXY_URL, backup: null };
|
|
1847
|
+
}
|
|
1848
|
+
}
|
|
1849
|
+
async function createSession(gatewayUrl) {
|
|
1850
|
+
const response = await fetch(`${gatewayUrl}/v1/session`, {
|
|
1803
1851
|
method: "POST",
|
|
1804
1852
|
headers: { "Content-Type": "application/json" },
|
|
1805
1853
|
});
|
|
@@ -1809,142 +1857,244 @@ async function createSession() {
|
|
|
1809
1857
|
const data = (await response.json());
|
|
1810
1858
|
return data.code;
|
|
1811
1859
|
}
|
|
1812
|
-
function displayQR(code) {
|
|
1860
|
+
function displayQR(primaryGateway, backupGateway, code) {
|
|
1861
|
+
const qrPayload = backupGateway
|
|
1862
|
+
? `${primaryGateway},${backupGateway},${code}`
|
|
1863
|
+
: `${primaryGateway},${code}`;
|
|
1813
1864
|
console.log("\n");
|
|
1814
|
-
qrcode.generate(
|
|
1865
|
+
qrcode.generate(qrPayload, { small: true }, (qr) => {
|
|
1815
1866
|
console.log(qr);
|
|
1816
1867
|
console.log(`\n Session code: ${code}\n`);
|
|
1868
|
+
console.log(` Primary gateway: ${primaryGateway}`);
|
|
1869
|
+
if (backupGateway) {
|
|
1870
|
+
console.log(` Backup gateway: ${backupGateway}`);
|
|
1871
|
+
}
|
|
1817
1872
|
console.log(` Root directory: ${ROOT_DIR}\n`);
|
|
1818
1873
|
console.log(" Scan the QR code with the Lunel app to connect.");
|
|
1819
1874
|
console.log(" Press Ctrl+C to exit.\n");
|
|
1820
1875
|
});
|
|
1821
1876
|
}
|
|
1822
|
-
function
|
|
1823
|
-
|
|
1824
|
-
const
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
const dataWs = new WebSocket(dataUrl);
|
|
1833
|
-
let dataConnected = false;
|
|
1834
|
-
// Store data channel reference for terminal output
|
|
1835
|
-
dataChannel = dataWs;
|
|
1836
|
-
function checkFullyConnected() {
|
|
1837
|
-
if (controlConnected && dataConnected) {
|
|
1838
|
-
console.log("Connected to proxy (control + data channels). Waiting for app...\n");
|
|
1877
|
+
function buildWsUrl(gatewayUrl, role, channel) {
|
|
1878
|
+
const wsBase = gatewayUrl.replace(/^http/, "ws");
|
|
1879
|
+
const query = new URLSearchParams();
|
|
1880
|
+
if (currentSessionPassword) {
|
|
1881
|
+
query.set("password", currentSessionPassword);
|
|
1882
|
+
}
|
|
1883
|
+
else if (currentSessionCode) {
|
|
1884
|
+
query.set("code", currentSessionCode);
|
|
1885
|
+
if (currentBackupGateway) {
|
|
1886
|
+
query.set("backup", currentBackupGateway);
|
|
1839
1887
|
}
|
|
1840
1888
|
}
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1889
|
+
else {
|
|
1890
|
+
throw new Error("missing code/password for websocket connect");
|
|
1891
|
+
}
|
|
1892
|
+
return `${wsBase}/v1/ws/${role}/${channel}?${query.toString()}`;
|
|
1893
|
+
}
|
|
1894
|
+
function gracefulShutdown() {
|
|
1895
|
+
shuttingDown = true;
|
|
1896
|
+
console.log("\nShutting down...");
|
|
1897
|
+
if (ptyProcess) {
|
|
1898
|
+
ptyProcess.kill();
|
|
1899
|
+
ptyProcess = null;
|
|
1900
|
+
}
|
|
1901
|
+
terminals.clear();
|
|
1902
|
+
for (const [pid, managedProc] of processes) {
|
|
1903
|
+
managedProc.proc.kill();
|
|
1904
|
+
}
|
|
1905
|
+
processes.clear();
|
|
1906
|
+
processOutputBuffers.clear();
|
|
1907
|
+
cleanupAllTunnels();
|
|
1908
|
+
if (activeControlWs && (activeControlWs.readyState === WebSocket.OPEN || activeControlWs.readyState === WebSocket.CONNECTING)) {
|
|
1909
|
+
activeControlWs.close();
|
|
1910
|
+
}
|
|
1911
|
+
if (activeDataWs && (activeDataWs.readyState === WebSocket.OPEN || activeDataWs.readyState === WebSocket.CONNECTING)) {
|
|
1912
|
+
activeDataWs.close();
|
|
1913
|
+
}
|
|
1914
|
+
process.exit(0);
|
|
1915
|
+
}
|
|
1916
|
+
async function connectWebSocket() {
|
|
1917
|
+
const gateways = currentBackupGateway ? [currentPrimaryGateway, currentBackupGateway] : [currentPrimaryGateway];
|
|
1918
|
+
const uniqueGateways = Array.from(new Set(gateways));
|
|
1919
|
+
for (const gatewayUrl of uniqueGateways) {
|
|
1847
1920
|
try {
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1921
|
+
await new Promise((resolve, reject) => {
|
|
1922
|
+
activeGatewayUrl = gatewayUrl;
|
|
1923
|
+
const controlUrl = buildWsUrl(gatewayUrl, "cli", "control");
|
|
1924
|
+
const dataUrl = buildWsUrl(gatewayUrl, "cli", "data");
|
|
1925
|
+
console.log(`Connecting to gateway ${gatewayUrl}...`);
|
|
1926
|
+
const controlWs = new WebSocket(controlUrl);
|
|
1927
|
+
const dataWs = new WebSocket(dataUrl);
|
|
1928
|
+
activeControlWs = controlWs;
|
|
1929
|
+
activeDataWs = dataWs;
|
|
1930
|
+
dataChannel = dataWs;
|
|
1931
|
+
let controlConnected = false;
|
|
1932
|
+
let dataConnected = false;
|
|
1933
|
+
let settled = false;
|
|
1934
|
+
let closeHandled = false;
|
|
1935
|
+
let closeReason = "";
|
|
1936
|
+
const failConnection = (reason) => {
|
|
1937
|
+
if (settled)
|
|
1938
|
+
return;
|
|
1939
|
+
settled = true;
|
|
1940
|
+
reject(new Error(reason));
|
|
1941
|
+
};
|
|
1942
|
+
const checkFullyConnected = () => {
|
|
1943
|
+
if (controlConnected && dataConnected && !settled) {
|
|
1944
|
+
settled = true;
|
|
1945
|
+
console.log("Connected to gateway (control + data channels).\n");
|
|
1946
|
+
resolve();
|
|
1947
|
+
}
|
|
1948
|
+
};
|
|
1949
|
+
const handleClose = (reason) => {
|
|
1950
|
+
if (closeHandled || shuttingDown)
|
|
1951
|
+
return;
|
|
1952
|
+
closeHandled = true;
|
|
1953
|
+
closeReason = reason;
|
|
1954
|
+
cleanupAllTunnels();
|
|
1955
|
+
setTimeout(() => {
|
|
1956
|
+
if (shuttingDown)
|
|
1957
|
+
return;
|
|
1958
|
+
void handleConnectionDrop(closeReason);
|
|
1959
|
+
}, 50);
|
|
1960
|
+
};
|
|
1961
|
+
controlWs.on("open", () => {
|
|
1962
|
+
controlConnected = true;
|
|
1963
|
+
checkFullyConnected();
|
|
1964
|
+
});
|
|
1965
|
+
controlWs.on("message", async (data) => {
|
|
1966
|
+
try {
|
|
1967
|
+
const message = JSON.parse(data.toString());
|
|
1968
|
+
if ("type" in message) {
|
|
1969
|
+
if (message.type === "connected")
|
|
1970
|
+
return;
|
|
1971
|
+
if (message.type === "session_password" && message.password) {
|
|
1972
|
+
currentSessionPassword = message.password;
|
|
1973
|
+
console.log("[session] received reconnect password");
|
|
1974
|
+
return;
|
|
1975
|
+
}
|
|
1976
|
+
if (message.type === "peer_connected") {
|
|
1977
|
+
console.log("App connected!\n");
|
|
1978
|
+
scanDevPorts().then((openPorts) => {
|
|
1979
|
+
if (openPorts.length > 0) {
|
|
1980
|
+
console.log(`Found ${openPorts.length} open dev port(s): ${openPorts.join(", ")}`);
|
|
1981
|
+
}
|
|
1982
|
+
if (controlWs.readyState === WebSocket.OPEN) {
|
|
1983
|
+
controlWs.send(JSON.stringify({
|
|
1984
|
+
v: 1,
|
|
1985
|
+
id: `evt-${Date.now()}`,
|
|
1986
|
+
ns: "proxy",
|
|
1987
|
+
action: "ports_discovered",
|
|
1988
|
+
payload: { ports: openPorts },
|
|
1989
|
+
}));
|
|
1990
|
+
}
|
|
1991
|
+
}).catch((err) => {
|
|
1992
|
+
console.error("Port scan failed:", err);
|
|
1993
|
+
});
|
|
1994
|
+
return;
|
|
1995
|
+
}
|
|
1996
|
+
if (message.type === "peer_disconnected") {
|
|
1997
|
+
console.log("App disconnected. Waiting for reconnect window.\n");
|
|
1998
|
+
return;
|
|
1999
|
+
}
|
|
2000
|
+
if (message.type === "app_disconnected") {
|
|
2001
|
+
if (message.reconnectDeadline) {
|
|
2002
|
+
console.log(`[session] app disconnected, waiting until ${new Date(message.reconnectDeadline).toISOString()}`);
|
|
2003
|
+
}
|
|
2004
|
+
return;
|
|
2005
|
+
}
|
|
2006
|
+
if (message.type === "close_connection") {
|
|
2007
|
+
console.log(`[session] closed by gateway: ${message.reason || "expired"}`);
|
|
2008
|
+
gracefulShutdown();
|
|
2009
|
+
return;
|
|
2010
|
+
}
|
|
2011
|
+
}
|
|
2012
|
+
if (message.v === 1) {
|
|
2013
|
+
const response = await processMessage(message);
|
|
2014
|
+
controlWs.send(JSON.stringify(response));
|
|
2015
|
+
}
|
|
1859
2016
|
}
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
v: 1,
|
|
1863
|
-
id: `evt-${Date.now()}`,
|
|
1864
|
-
ns: "proxy",
|
|
1865
|
-
action: "ports_discovered",
|
|
1866
|
-
payload: { ports: openPorts },
|
|
1867
|
-
}));
|
|
2017
|
+
catch (error) {
|
|
2018
|
+
console.error("Error processing control message:", error);
|
|
1868
2019
|
}
|
|
1869
|
-
}).catch((err) => {
|
|
1870
|
-
console.error("Port scan failed:", err);
|
|
1871
2020
|
});
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
dataWs.on("error", (error) => {
|
|
1926
|
-
console.error("Data WebSocket error:", error.message);
|
|
1927
|
-
});
|
|
1928
|
-
// Handle graceful shutdown
|
|
1929
|
-
process.on("SIGINT", () => {
|
|
1930
|
-
console.log("\nShutting down...");
|
|
1931
|
-
// Kill PTY process (kills all terminals)
|
|
1932
|
-
if (ptyProcess) {
|
|
1933
|
-
ptyProcess.kill();
|
|
1934
|
-
ptyProcess = null;
|
|
2021
|
+
controlWs.on("close", (code, reason) => {
|
|
2022
|
+
if (!settled) {
|
|
2023
|
+
failConnection(`control close before ready (${code}: ${reason.toString()})`);
|
|
2024
|
+
return;
|
|
2025
|
+
}
|
|
2026
|
+
handleClose(`control closed (${code}: ${reason.toString()})`);
|
|
2027
|
+
});
|
|
2028
|
+
controlWs.on("error", (error) => {
|
|
2029
|
+
if (!settled) {
|
|
2030
|
+
failConnection(`control ws error: ${error.message}`);
|
|
2031
|
+
return;
|
|
2032
|
+
}
|
|
2033
|
+
console.error("Control WebSocket error:", error.message);
|
|
2034
|
+
});
|
|
2035
|
+
dataWs.on("open", () => {
|
|
2036
|
+
dataConnected = true;
|
|
2037
|
+
checkFullyConnected();
|
|
2038
|
+
});
|
|
2039
|
+
dataWs.on("message", async (data) => {
|
|
2040
|
+
try {
|
|
2041
|
+
const message = JSON.parse(data.toString());
|
|
2042
|
+
if (message.type === "connected")
|
|
2043
|
+
return;
|
|
2044
|
+
if (message.v === 1) {
|
|
2045
|
+
const response = await processMessage(message);
|
|
2046
|
+
dataWs.send(JSON.stringify(response));
|
|
2047
|
+
}
|
|
2048
|
+
}
|
|
2049
|
+
catch (error) {
|
|
2050
|
+
console.error("Error processing data message:", error);
|
|
2051
|
+
}
|
|
2052
|
+
});
|
|
2053
|
+
dataWs.on("close", (code, reason) => {
|
|
2054
|
+
if (!settled) {
|
|
2055
|
+
failConnection(`data close before ready (${code}: ${reason.toString()})`);
|
|
2056
|
+
return;
|
|
2057
|
+
}
|
|
2058
|
+
handleClose(`data closed (${code}: ${reason.toString()})`);
|
|
2059
|
+
});
|
|
2060
|
+
dataWs.on("error", (error) => {
|
|
2061
|
+
if (!settled) {
|
|
2062
|
+
failConnection(`data ws error: ${error.message}`);
|
|
2063
|
+
return;
|
|
2064
|
+
}
|
|
2065
|
+
console.error("Data WebSocket error:", error.message);
|
|
2066
|
+
});
|
|
2067
|
+
setTimeout(() => {
|
|
2068
|
+
if (!settled) {
|
|
2069
|
+
failConnection("connection timeout");
|
|
2070
|
+
}
|
|
2071
|
+
}, 10000);
|
|
2072
|
+
});
|
|
2073
|
+
return;
|
|
1935
2074
|
}
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
for (const [pid, managedProc] of processes) {
|
|
1939
|
-
managedProc.proc.kill();
|
|
2075
|
+
catch (err) {
|
|
2076
|
+
console.error(`[gateway] failed ${gatewayUrl}: ${err.message}`);
|
|
1940
2077
|
}
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
});
|
|
2078
|
+
}
|
|
2079
|
+
throw new Error("unable to connect to any gateway");
|
|
2080
|
+
}
|
|
2081
|
+
async function handleConnectionDrop(reason) {
|
|
2082
|
+
if (shuttingDown)
|
|
2083
|
+
return;
|
|
2084
|
+
console.log(`\nDisconnected: ${reason}`);
|
|
2085
|
+
if (!currentSessionPassword) {
|
|
2086
|
+
console.error("No reconnect password available. Exiting.");
|
|
2087
|
+
gracefulShutdown();
|
|
2088
|
+
return;
|
|
2089
|
+
}
|
|
2090
|
+
try {
|
|
2091
|
+
await connectWebSocket();
|
|
2092
|
+
console.log(`[reconnect] connected via ${activeGatewayUrl}`);
|
|
2093
|
+
}
|
|
2094
|
+
catch (err) {
|
|
2095
|
+
console.error(`[reconnect] failed on all gateways: ${err.message}`);
|
|
2096
|
+
gracefulShutdown();
|
|
2097
|
+
}
|
|
1948
2098
|
}
|
|
1949
2099
|
async function main() {
|
|
1950
2100
|
console.log("Lunel CLI v" + VERSION);
|
|
@@ -1976,9 +2126,14 @@ async function main() {
|
|
|
1976
2126
|
console.log("OpenCode ready.\n");
|
|
1977
2127
|
// Subscribe to OpenCode events
|
|
1978
2128
|
subscribeToOpenCodeEvents(client);
|
|
1979
|
-
const
|
|
1980
|
-
|
|
1981
|
-
|
|
2129
|
+
const gateways = await discoverGateways();
|
|
2130
|
+
currentPrimaryGateway = gateways.primary;
|
|
2131
|
+
currentBackupGateway = gateways.backup;
|
|
2132
|
+
activeGatewayUrl = currentPrimaryGateway;
|
|
2133
|
+
const code = await createSession(currentPrimaryGateway);
|
|
2134
|
+
currentSessionCode = code;
|
|
2135
|
+
displayQR(currentPrimaryGateway, currentBackupGateway, code);
|
|
2136
|
+
await connectWebSocket();
|
|
1982
2137
|
}
|
|
1983
2138
|
catch (error) {
|
|
1984
2139
|
if (error instanceof Error) {
|
|
@@ -1992,4 +2147,6 @@ async function main() {
|
|
|
1992
2147
|
process.exit(1);
|
|
1993
2148
|
}
|
|
1994
2149
|
}
|
|
2150
|
+
process.on("SIGINT", gracefulShutdown);
|
|
2151
|
+
process.on("SIGTERM", gracefulShutdown);
|
|
1995
2152
|
main();
|