superbrain-server 1.0.6 → 1.0.7

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/package.json +1 -1
  2. package/payload/start.py +62 -50
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "superbrain-server",
3
- "version": "1.0.6",
3
+ "version": "1.0.7",
4
4
  "description": "1-Line Auto-Installer and Server Execution wrapper for SuperBrain",
5
5
  "main": "index.js",
6
6
  "bin": {
package/payload/start.py CHANGED
@@ -666,7 +666,7 @@ LOCALTUNNEL_ENABLED = BASE_DIR / "config" / "localtunnel_enabled.txt"
666
666
  LOCALTUNNEL_LOG = BASE_DIR / "config" / "localtunnel.log"
667
667
 
668
668
  def setup_remote_access():
669
- h1("Step 6 of 7 — Remote Access (localtunnel / Port Forwarding)")
669
+ h1("Step 6 of 7 — Remote Access (localhost.run / Port Forwarding)")
670
670
 
671
671
  print(f"""
672
672
  The SuperBrain backend runs on {BOLD}port 5000{RESET} on your machine.
@@ -674,10 +674,10 @@ def setup_remote_access():
674
674
 
675
675
  You have two options:
676
676
 
677
- {BOLD}Option A — localtunnel (easiest + free){RESET}
678
- localtunnel creates a public HTTPS URL that tunnels to your local port 5000.
677
+ {BOLD}Option A — localhost.run (easiest + free ssh tunnel){RESET}
678
+ localhost.run creates a public HTTPS URL seamlessly using SSH.
679
679
  No account required.
680
- Official site: {CYAN}https://theboroer.github.io/localtunnel-www/{RESET}
680
+ Official site: {CYAN}https://localhost.run/{RESET}
681
681
 
682
682
  {BOLD}Option B — Your own port forwarding (advanced){RESET}
683
683
  Forward {BOLD}TCP port 5000{RESET} on your router to your machine's local IP.
@@ -693,10 +693,10 @@ def setup_remote_access():
693
693
  the same network. Use your PC's local IP (e.g. 192.168.x.x) in the app.{RESET}
694
694
  """)
695
695
 
696
- choice = ask_yn("Enable localtunnel on startup?", default=True)
696
+ choice = ask_yn("Enable localhost.run on startup?", default=True)
697
697
  if not choice:
698
698
  LOCALTUNNEL_ENABLED.unlink(missing_ok=True)
699
- warn("Skipping localtunnel. Use either your own port forwarding or local WiFi.")
699
+ warn("Skipping localhost.run. Use either your own port forwarding or local WiFi.")
700
700
  info("Remember: set the correct server URL in the mobile app Settings.")
701
701
  return
702
702
 
@@ -711,15 +711,15 @@ def setup_remote_access():
711
711
 
712
712
  After installing, re-run {BOLD}python start.py{RESET}.
713
713
  """)
714
- warn("Skipping localtunnel setup.")
714
+ warn("Skipping localhost.run setup.")
715
715
  return
716
716
 
717
717
  ok("npx binary found")
718
718
  LOCALTUNNEL_ENABLED.parent.mkdir(parents=True, exist_ok=True)
719
719
  LOCALTUNNEL_ENABLED.write_text("enabled")
720
- ok("localtunnel auto-start enabled")
720
+ ok("localhost.run auto-start enabled")
721
721
  nl()
722
- info("localtunnel will be started automatically every time you run start.py.")
722
+ info("localhost.run will be started automatically every time you run start.py.")
723
723
 
724
724
  # ══════════════════════════════════════════════════════════════════════════════
725
725
  # Step 6 — Access Token & Database
@@ -754,83 +754,95 @@ def setup_token_and_db():
754
754
  # ══════════════════════════════════════════════════════════════════════════════
755
755
  # Launch Backend
756
756
  # ══════════════════════════════════════════════════════════════════════════════
757
- def _extract_localtunnel_url(text: str) -> str | None:
758
- """Extract first localtunnel public URL from text."""
757
+ def _extract_localhost_run_url(text: str) -> str | None:
758
+ """Extract first localhost.run public URL from text."""
759
759
  import re
760
- m = re.search(r"https://[\w.-]+\.loca\.lt\b", text)
761
- return m.group(0) if m else None
760
+ lines = text.splitlines()
761
+ for line in reversed(lines):
762
+ if "tunneled with tls termination" in line or ".lhr.life" in line or ".localhost.run" in line:
763
+ match = re.search(r'(https://[a-zA-Z0-9-]+\.(?:lhr\.life|localhost\.run))', line)
764
+ if match:
765
+ return match.group(1)
766
+ return None
762
767
 
763
768
 
764
- def _find_localtunnel_url_from_log() -> str | None:
769
+ def _find_localhost_run_url_from_log() -> str | None:
765
770
  """Read local tunnel log and return detected public URL if available."""
766
771
  try:
767
772
  if not LOCALTUNNEL_LOG.exists():
768
773
  return None
769
774
  text = LOCALTUNNEL_LOG.read_text(encoding="utf-8", errors="ignore")
770
- return _extract_localtunnel_url(text)
775
+ return _extract_localhost_run_url(text)
771
776
  except Exception:
772
777
  return None
773
778
 
774
779
 
775
- def _stop_localtunnel_processes():
776
- """Stop existing localtunnel processes so only one tunnel remains active."""
780
+ def _stop_localhost_run_processes():
781
+ """Stop existing localhost.run processes so only one tunnel remains active."""
777
782
  try:
778
783
  if IS_WINDOWS:
779
784
  script = (
780
785
  "Get-CimInstance Win32_Process "
781
- "| Where-Object { $_.CommandLine -match 'localtunnel|\\.loca\\.lt' } "
786
+ "| Where-Object { $_.CommandLine -match 'nokey@localhost.run' } "
782
787
  "| ForEach-Object { Stop-Process -Id $_.ProcessId -Force -ErrorAction SilentlyContinue }"
783
788
  )
784
789
  subprocess.run(["powershell", "-NoProfile", "-Command", script], check=False)
785
790
  else:
786
- subprocess.run(["pkill", "-f", "localtunnel"], check=False)
791
+ subprocess.run(["pkill", "-f", "nokey@localhost.run"], check=False)
787
792
  except Exception:
788
793
  pass
789
794
 
790
795
 
791
- def _start_localtunnel(port: int, timeout: int = 25) -> str | None:
792
- """Start localtunnel in the background and wait for the public URL."""
796
+ def _start_localhost_run(port: int, timeout: int = 25) -> str | None:
797
+ """Start localhost.run in the background via SSH and wait for the public URL."""
793
798
  import time as _time
794
799
 
795
- npx_exec = shutil.which("npx") or shutil.which("npx.cmd")
796
- if not npx_exec:
800
+ ssh_exec = shutil.which("ssh")
801
+ if not ssh_exec:
802
+ warn("SSH is required to use localhost.run. Please install OpenSSH.")
797
803
  return None
798
804
 
799
- # Clean stale localtunnel processes.
800
- _stop_localtunnel_processes()
805
+ # Clean stale ssh tunnel processes.
806
+ _stop_localhost_run_processes()
801
807
  _time.sleep(0.8)
802
808
 
803
- info("Starting localtunnel in background …")
809
+ info("Starting localhost.run SSH tunnel in background …")
804
810
  try:
805
811
  LOCALTUNNEL_LOG.parent.mkdir(parents=True, exist_ok=True)
806
812
  LOCALTUNNEL_LOG.write_text("")
807
813
 
808
814
  log_handle = open(LOCALTUNNEL_LOG, "a", encoding="utf-8", buffering=1)
809
815
  kwargs = {
810
- "start_new_session": True,
811
816
  "stdout": log_handle,
812
817
  "stderr": subprocess.STDOUT,
818
+ "stdin": subprocess.DEVNULL,
813
819
  "text": True,
814
820
  }
815
- if IS_WINDOWS and npx_exec.lower().endswith(".cmd"):
816
- cmd = ["cmd", "/c", npx_exec, "-y", "localtunnel", "--port", str(port)]
817
- else:
818
- cmd = [npx_exec, "-y", "localtunnel", "--port", str(port)]
819
- subprocess.Popen(cmd, **kwargs)
821
+ if IS_WINDOWS:
822
+ kwargs["creationflags"] = subprocess.CREATE_NEW_PROCESS_GROUP
823
+
824
+ cmd = [ssh_exec, "-o", "StrictHostKeyChecking=no", "-R", f"80:localhost:{port}", "nokey@localhost.run"]
825
+ process = subprocess.Popen(cmd, **kwargs)
826
+
827
+ _time.sleep(1.0)
828
+ if process.poll() is not None:
829
+ warn(f"Could not start localhost.run, exited with {process.returncode}")
830
+ return None
831
+
820
832
  except Exception as e:
821
- warn(f"Could not start localtunnel: {e}")
833
+ warn(f"Could not start localhost.run: {e}")
822
834
  return None
823
835
 
824
836
  # Poll log output until URL is emitted.
825
837
  deadline = _time.time() + timeout
826
838
  while _time.time() < deadline:
827
839
  _time.sleep(1)
828
- url = _find_localtunnel_url_from_log()
840
+ url = _find_localhost_run_url_from_log()
829
841
  if url:
830
- ok(f"localtunnel active → {GREEN}{BOLD}{url}{RESET}")
842
+ ok(f"localhost.run active → {GREEN}{BOLD}{url}{RESET}")
831
843
  return url
832
844
 
833
- warn("localtunnel started but URL is not available yet.")
845
+ warn("localhost.run started but URL is not available yet.")
834
846
  info(f"Check tunnel logs in: {LOCALTUNNEL_LOG}")
835
847
  return None
836
848
 
@@ -1202,26 +1214,26 @@ def launch_backend():
1202
1214
  token = TOKEN_FILE.read_text().strip() if TOKEN_FILE.exists() else "—"
1203
1215
  local_ip = _detect_local_ip()
1204
1216
 
1205
- localtunnel_enabled = bool(shutil.which("npx") or shutil.which("npx.cmd"))
1217
+ localhost_run_enabled = bool(shutil.which("ssh"))
1206
1218
 
1207
- localtunnel_url: str | None = None
1208
- if localtunnel_enabled:
1209
- localtunnel_url = _start_localtunnel(PORT)
1219
+ localhost_run_url: str | None = None
1220
+ if localhost_run_enabled:
1221
+ localhost_run_url = _start_localhost_run(PORT)
1210
1222
  else:
1211
- localtunnel_url = _find_localtunnel_url_from_log()
1223
+ localhost_run_url = _find_localhost_run_url_from_log()
1212
1224
 
1213
- if localtunnel_url:
1214
- tunnel_line = f" Public URL → {GREEN}{BOLD}{localtunnel_url}{RESET} {DIM}(localtunnel){RESET}"
1215
- tunnel_hint = f" · public → {GREEN}{localtunnel_url}{RESET}"
1216
- elif localtunnel_enabled:
1225
+ if localhost_run_url:
1226
+ tunnel_line = f" Public URL → {GREEN}{BOLD}{localhost_run_url}{RESET} {DIM}(localhost.run){RESET}"
1227
+ tunnel_hint = f" · public → {GREEN}{localhost_run_url}{RESET}"
1228
+ elif localhost_run_enabled:
1217
1229
  tunnel_line = f" Public URL → {YELLOW}(starting — URL pending, check localtunnel.log){RESET}"
1218
- tunnel_hint = f" · public → run: {DIM}npx localtunnel --port {PORT}{RESET}"
1230
+ tunnel_hint = f" · public → run: {DIM}ssh -R 80:localhost:{PORT} nokey@localhost.run{RESET}"
1219
1231
  else:
1220
1232
  tunnel_line = ""
1221
- tunnel_hint = f" · public → install Node.js first, then run: {DIM}npx localtunnel --port {PORT}{RESET}"
1233
+ tunnel_hint = f" · public → install OpenSSH first, then run: {DIM}ssh -R 80:localhost:{PORT} nokey@localhost.run{RESET}"
1222
1234
 
1223
1235
  # ── Generate and display QR code ──────────────────────────────────────────
1224
- qr_url = localtunnel_url if localtunnel_url else f"http://{local_ip}:{PORT}"
1236
+ qr_url = localhost_run_url if localhost_run_url else f"http://{local_ip}:{PORT}"
1225
1237
  _display_connect_qr(qr_url, token)
1226
1238
 
1227
1239
  print(f"""
@@ -1282,7 +1294,7 @@ def launch_backend_status():
1282
1294
  url = match.group(1)
1283
1295
 
1284
1296
  if url == "NOT_FOUND":
1285
- warn("Could not find a running localtunnel URL in config/localtunnel.log.")
1297
+ warn("Could not find a running localhost.run URL in config/localtunnel.log.")
1286
1298
  nl()
1287
1299
  print(" Wait 5 seconds, or run 'superbrain-server' to start the server.")
1288
1300
  return
@@ -1327,7 +1339,7 @@ def main():
1327
1339
  3 · Configure AI provider keys + Instagram credentials
1328
1340
  4 · Set up an offline AI model via Ollama (qwen3-vl:4b)
1329
1341
  5 · Set up offline audio transcription (Whisper + ffmpeg)
1330
- 6 · Configure remote access (localtunnel or port forwarding)
1342
+ 6 · Configure remote access (localhost.run or port forwarding)
1331
1343
  7 · Generate Access Token & initialise database
1332
1344
 
1333
1345
  Press {BOLD}Enter{RESET} to accept defaults shown in [{DIM}brackets{RESET}].