lunel-cli 0.1.30 → 0.1.31

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.
Files changed (2) hide show
  1. package/dist/index.js +104 -54
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -13,6 +13,7 @@ import { createServer, createConnection } from "net";
13
13
  import { createInterface } from "readline";
14
14
  const DEFAULT_PROXY_URL = process.env.LUNEL_PROXY_URL || "https://gateway.lunel.dev";
15
15
  const MANAGER_URL = process.env.LUNEL_MANAGER_URL || "https://manager.lunel.dev";
16
+ const CLI_ARGS = process.argv.slice(2);
16
17
  import { createRequire } from "module";
17
18
  const __require = createRequire(import.meta.url);
18
19
  const VERSION = __require("../package.json").version;
@@ -39,6 +40,10 @@ let shuttingDown = false;
39
40
  let activeControlWs = null;
40
41
  let activeDataWs = null;
41
42
  const activeTunnels = new Map();
43
+ const PORT_SYNC_INTERVAL_MS = 30_000;
44
+ let portSyncTimer = null;
45
+ let portScanInFlight = false;
46
+ let lastDiscoveredPorts = [];
42
47
  // Popular development server ports to scan on connect
43
48
  const DEV_PORTS = [
44
49
  1234, // Parcel
@@ -88,6 +93,44 @@ const DEV_PORTS = [
88
93
  19006, // Expo web
89
94
  24678, // Vite HMR WebSocket
90
95
  ];
96
+ function parseExtraPortsFromArgs(args) {
97
+ const values = [];
98
+ for (let i = 0; i < args.length; i++) {
99
+ const arg = args[i];
100
+ if (arg.startsWith("--extra-ports=")) {
101
+ values.push(arg.slice("--extra-ports=".length));
102
+ continue;
103
+ }
104
+ if (arg === "--extra-ports" && i + 1 < args.length) {
105
+ values.push(args[i + 1]);
106
+ i++;
107
+ }
108
+ }
109
+ const parsed = new Set();
110
+ for (const value of values) {
111
+ for (const piece of value.split(",")) {
112
+ const trimmed = piece.trim();
113
+ if (!trimmed)
114
+ continue;
115
+ const num = Number(trimmed);
116
+ if (!Number.isInteger(num) || num < 1 || num > 65535)
117
+ continue;
118
+ parsed.add(num);
119
+ }
120
+ }
121
+ return Array.from(parsed).sort((a, b) => a - b);
122
+ }
123
+ const EXTRA_PORTS = parseExtraPortsFromArgs(CLI_ARGS);
124
+ const SCAN_PORTS = Array.from(new Set([...DEV_PORTS, ...EXTRA_PORTS])).sort((a, b) => a - b);
125
+ function samePortSet(a, b) {
126
+ if (a.length !== b.length)
127
+ return false;
128
+ for (let i = 0; i < a.length; i++) {
129
+ if (a[i] !== b[i])
130
+ return false;
131
+ }
132
+ return true;
133
+ }
91
134
  // ============================================================================
92
135
  // Path Safety
93
136
  // ============================================================================
@@ -1413,7 +1456,7 @@ async function subscribeToOpenCodeEvents(client) {
1413
1456
  // ============================================================================
1414
1457
  async function scanDevPorts() {
1415
1458
  const openPorts = [];
1416
- const checks = DEV_PORTS.map((port) => {
1459
+ const checks = SCAN_PORTS.map((port) => {
1417
1460
  return new Promise((resolve) => {
1418
1461
  const socket = createConnection({ port, host: "127.0.0.1" });
1419
1462
  socket.setTimeout(200);
@@ -1434,6 +1477,48 @@ async function scanDevPorts() {
1434
1477
  await Promise.all(checks);
1435
1478
  return openPorts.sort((a, b) => a - b);
1436
1479
  }
1480
+ async function publishDiscoveredPorts(ws, force = false) {
1481
+ if (portScanInFlight)
1482
+ return;
1483
+ if (ws.readyState !== WebSocket.OPEN)
1484
+ return;
1485
+ portScanInFlight = true;
1486
+ try {
1487
+ const openPorts = await scanDevPorts();
1488
+ if (!force && samePortSet(openPorts, lastDiscoveredPorts)) {
1489
+ return;
1490
+ }
1491
+ lastDiscoveredPorts = openPorts;
1492
+ ws.send(JSON.stringify({
1493
+ v: 1,
1494
+ id: `evt-${Date.now()}`,
1495
+ ns: "proxy",
1496
+ action: "ports_discovered",
1497
+ payload: { ports: openPorts },
1498
+ }));
1499
+ console.log(`[proxy] ports updated (${openPorts.length}): ${openPorts.join(", ") || "-"}`);
1500
+ }
1501
+ catch (err) {
1502
+ console.error("Port scan failed:", err);
1503
+ }
1504
+ finally {
1505
+ portScanInFlight = false;
1506
+ }
1507
+ }
1508
+ function stopPortSync() {
1509
+ if (portSyncTimer) {
1510
+ clearInterval(portSyncTimer);
1511
+ portSyncTimer = null;
1512
+ }
1513
+ portScanInFlight = false;
1514
+ }
1515
+ function startPortSync(ws) {
1516
+ stopPortSync();
1517
+ void publishDiscoveredPorts(ws, true);
1518
+ portSyncTimer = setInterval(() => {
1519
+ void publishDiscoveredPorts(ws, false);
1520
+ }, PORT_SYNC_INTERVAL_MS);
1521
+ }
1437
1522
  async function handleProxyConnect(payload) {
1438
1523
  const tunnelId = payload.tunnelId;
1439
1524
  const port = payload.port;
@@ -1824,45 +1909,19 @@ function normalizeGatewayUrl(input) {
1824
1909
  return input.replace(/\/+$/, "");
1825
1910
  return `https://${input}`.replace(/\/+$/, "");
1826
1911
  }
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`, {
1912
+ async function createSessionFromManager() {
1913
+ const response = await fetch(`${MANAGER_URL}/v1/session`, {
1851
1914
  method: "POST",
1852
1915
  headers: { "Content-Type": "application/json" },
1853
1916
  });
1854
1917
  if (!response.ok) {
1855
- throw new Error(`Failed to create session: ${response.status}`);
1918
+ throw new Error(`Failed to create session from manager: ${response.status}`);
1856
1919
  }
1857
- const data = (await response.json());
1858
- return data.code;
1920
+ return (await response.json());
1859
1921
  }
1860
1922
  function displayQR(primaryGateway, backupGateway, code) {
1861
- const qrPayload = backupGateway
1862
- ? `${primaryGateway},${backupGateway},${code}`
1863
- : `${primaryGateway},${code}`;
1864
1923
  console.log("\n");
1865
- qrcode.generate(qrPayload, { small: true }, (qr) => {
1924
+ qrcode.generate(code, { small: true }, (qr) => {
1866
1925
  console.log(qr);
1867
1926
  console.log(`\n Session code: ${code}\n`);
1868
1927
  console.log(` Primary gateway: ${primaryGateway}`);
@@ -1894,6 +1953,7 @@ function buildWsUrl(gatewayUrl, role, channel) {
1894
1953
  function gracefulShutdown() {
1895
1954
  shuttingDown = true;
1896
1955
  console.log("\nShutting down...");
1956
+ stopPortSync();
1897
1957
  if (ptyProcess) {
1898
1958
  ptyProcess.kill();
1899
1959
  ptyProcess = null;
@@ -1951,6 +2011,7 @@ async function connectWebSocket() {
1951
2011
  return;
1952
2012
  closeHandled = true;
1953
2013
  closeReason = reason;
2014
+ stopPortSync();
1954
2015
  cleanupAllTunnels();
1955
2016
  setTimeout(() => {
1956
2017
  if (shuttingDown)
@@ -1975,26 +2036,12 @@ async function connectWebSocket() {
1975
2036
  }
1976
2037
  if (message.type === "peer_connected") {
1977
2038
  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
- });
2039
+ startPortSync(controlWs);
1994
2040
  return;
1995
2041
  }
1996
2042
  if (message.type === "peer_disconnected") {
1997
2043
  console.log("App disconnected. Waiting for reconnect window.\n");
2044
+ stopPortSync();
1998
2045
  return;
1999
2046
  }
2000
2047
  if (message.type === "app_disconnected") {
@@ -2099,6 +2146,9 @@ async function handleConnectionDrop(reason) {
2099
2146
  async function main() {
2100
2147
  console.log("Lunel CLI v" + VERSION);
2101
2148
  console.log("=".repeat(20) + "\n");
2149
+ if (EXTRA_PORTS.length > 0) {
2150
+ console.log(`Extra ports enabled: ${EXTRA_PORTS.join(", ")}`);
2151
+ }
2102
2152
  try {
2103
2153
  // Generate auth credentials (like CodeNomad does)
2104
2154
  const opencodeUsername = "lunel";
@@ -2126,13 +2176,13 @@ async function main() {
2126
2176
  console.log("OpenCode ready.\n");
2127
2177
  // Subscribe to OpenCode events
2128
2178
  subscribeToOpenCodeEvents(client);
2129
- const gateways = await discoverGateways();
2130
- currentPrimaryGateway = gateways.primary;
2131
- currentBackupGateway = gateways.backup;
2179
+ const session = await createSessionFromManager();
2180
+ currentPrimaryGateway = normalizeGatewayUrl(session.primary);
2181
+ currentBackupGateway = session.backup ? normalizeGatewayUrl(session.backup) : null;
2182
+ currentSessionPassword = session.password;
2132
2183
  activeGatewayUrl = currentPrimaryGateway;
2133
- const code = await createSession(currentPrimaryGateway);
2134
- currentSessionCode = code;
2135
- displayQR(currentPrimaryGateway, currentBackupGateway, code);
2184
+ currentSessionCode = session.code;
2185
+ displayQR(currentPrimaryGateway, currentBackupGateway, session.code);
2136
2186
  await connectWebSocket();
2137
2187
  }
2138
2188
  catch (error) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lunel-cli",
3
- "version": "0.1.30",
3
+ "version": "0.1.31",
4
4
  "author": [
5
5
  {
6
6
  "name": "Soham Bharambe",