superbrain-server 1.0.13 → 1.0.15
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/package.json +1 -1
- package/payload/requirements.txt +4 -1
- package/payload/start.py +166 -14
package/package.json
CHANGED
package/payload/requirements.txt
CHANGED
|
@@ -34,6 +34,9 @@ instagrapi>=2.0.0
|
|
|
34
34
|
opencv-python-headless>=4.9.0.80
|
|
35
35
|
Pillow>=10.0.0
|
|
36
36
|
|
|
37
|
+
# ── Remote Access (Networking) ──────────────────────────────────────────────
|
|
38
|
+
pyngrok>=7.1.5
|
|
39
|
+
|
|
37
40
|
# ── Terminal UI ───────────────────────────────────────────────────────────────
|
|
38
41
|
rich>=13.0.0
|
|
39
|
-
segno>=1.6.0
|
|
42
|
+
segno>=1.6.0
|
package/payload/start.py
CHANGED
|
@@ -666,13 +666,16 @@ def setup_whisper():
|
|
|
666
666
|
# ══════════════════════════════════════════════════════════════════════════════
|
|
667
667
|
NGROK_ENABLED = BASE_DIR / "config" / "ngrok_enabled.txt"
|
|
668
668
|
NGROK_TOKEN = BASE_DIR / "config" / "ngrok_token.txt"
|
|
669
|
+
LOCALTUNNEL_LOG = BASE_DIR / "config" / "localtunnel.log"
|
|
670
|
+
LOCALTUNNEL_LOG = BASE_DIR / "config" / "localtunnel.log"
|
|
669
671
|
|
|
670
672
|
def setup_remote_access():
|
|
671
|
-
h1("Step 6 of 7 — Remote Access (ngrok)")
|
|
673
|
+
h1("Step 6 of 7 — Remote Access (ngrok / localtunnel)")
|
|
672
674
|
|
|
673
675
|
print(f"""
|
|
674
676
|
The SuperBrain backend runs locally. Your phone needs to reach it over the internet.
|
|
675
677
|
We recommend {BOLD}ngrok{RESET} for a secure tunnel.
|
|
678
|
+
If ngrok is not configured or fails, {BOLD}localtunnel{RESET} will be used as a fallback.
|
|
676
679
|
|
|
677
680
|
Requires a free account from: {CYAN}https://dashboard.ngrok.com/signup{RESET}
|
|
678
681
|
Get your Authtoken at: {CYAN}https://dashboard.ngrok.com/get-started/your-authtoken{RESET}
|
|
@@ -681,7 +684,7 @@ def setup_remote_access():
|
|
|
681
684
|
choice = ask_yn("Enable ngrok on startup?", default=True)
|
|
682
685
|
if not choice:
|
|
683
686
|
NGROK_ENABLED.unlink(missing_ok=True)
|
|
684
|
-
warn("Skipping ngrok.
|
|
687
|
+
warn("Skipping ngrok. Will use localtunnel as fallback.")
|
|
685
688
|
return
|
|
686
689
|
|
|
687
690
|
ok("ngrok auto-start enabled")
|
|
@@ -733,7 +736,135 @@ def setup_token_and_db():
|
|
|
733
736
|
# ══════════════════════════════════════════════════════════════════════════════
|
|
734
737
|
# Launch Backend
|
|
735
738
|
# ══════════════════════════════════════════════════════════════════════════════
|
|
736
|
-
def
|
|
739
|
+
def _find_localtunnel_url_from_log() -> str | None:
|
|
740
|
+
try:
|
|
741
|
+
import re
|
|
742
|
+
if not LOCALTUNNEL_LOG.exists():
|
|
743
|
+
return None
|
|
744
|
+
text = LOCALTUNNEL_LOG.read_text(encoding="utf-8", errors="ignore")
|
|
745
|
+
m = re.search(r"https://[\w.-]+\.loca\.lt\b", text)
|
|
746
|
+
return m.group(0) if m else None
|
|
747
|
+
except Exception:
|
|
748
|
+
return None
|
|
749
|
+
|
|
750
|
+
def _stop_localtunnel_processes():
|
|
751
|
+
try:
|
|
752
|
+
if IS_WINDOWS:
|
|
753
|
+
script = (
|
|
754
|
+
"Get-CimInstance Win32_Process "
|
|
755
|
+
"| Where-Object { $_.CommandLine -match 'localtunnel|\\.loca\\.lt' } "
|
|
756
|
+
"| ForEach-Object { Stop-Process -Id $_.ProcessId -Force -ErrorAction SilentlyContinue }"
|
|
757
|
+
)
|
|
758
|
+
subprocess.run(["powershell", "-NoProfile", "-Command", script], check=False)
|
|
759
|
+
else:
|
|
760
|
+
subprocess.run(["pkill", "-f", "localtunnel"], check=False)
|
|
761
|
+
except Exception:
|
|
762
|
+
pass
|
|
763
|
+
|
|
764
|
+
def _start_localtunnel(port: int, timeout: int = 25) -> str | None:
|
|
765
|
+
import time
|
|
766
|
+
npx_exec = shutil.which("npx") or shutil.which("npx.cmd")
|
|
767
|
+
if not npx_exec:
|
|
768
|
+
warn("Node.js (npx) not found. Cannot start localtunnel.")
|
|
769
|
+
return None
|
|
770
|
+
|
|
771
|
+
_stop_localtunnel_processes()
|
|
772
|
+
time.sleep(0.8)
|
|
773
|
+
|
|
774
|
+
info("Starting localtunnel in background...")
|
|
775
|
+
try:
|
|
776
|
+
LOCALTUNNEL_LOG.parent.mkdir(parents=True, exist_ok=True)
|
|
777
|
+
LOCALTUNNEL_LOG.write_text("")
|
|
778
|
+
log_handle = open(LOCALTUNNEL_LOG, "a", encoding="utf-8", buffering=1)
|
|
779
|
+
kwargs = {
|
|
780
|
+
"start_new_session": True,
|
|
781
|
+
"stdout": log_handle,
|
|
782
|
+
"stderr": subprocess.STDOUT,
|
|
783
|
+
"text": True,
|
|
784
|
+
}
|
|
785
|
+
if IS_WINDOWS and npx_exec.lower().endswith(".cmd"):
|
|
786
|
+
cmd = ["cmd", "/c", npx_exec, "-y", "localtunnel", "--port", str(port)]
|
|
787
|
+
else:
|
|
788
|
+
cmd = [npx_exec, "-y", "localtunnel", "--port", str(port)]
|
|
789
|
+
subprocess.Popen(cmd, **kwargs)
|
|
790
|
+
except Exception as e:
|
|
791
|
+
warn(f"Could not start localtunnel: {e}")
|
|
792
|
+
return None
|
|
793
|
+
|
|
794
|
+
deadline = time.time() + timeout
|
|
795
|
+
while time.time() < deadline:
|
|
796
|
+
time.sleep(1)
|
|
797
|
+
url = _find_localtunnel_url_from_log()
|
|
798
|
+
if url:
|
|
799
|
+
return url
|
|
800
|
+
return None
|
|
801
|
+
|
|
802
|
+
def _find_localtunnel_url_from_log() -> str | None:
|
|
803
|
+
try:
|
|
804
|
+
import re
|
|
805
|
+
if not LOCALTUNNEL_LOG.exists():
|
|
806
|
+
return None
|
|
807
|
+
text = LOCALTUNNEL_LOG.read_text(encoding="utf-8", errors="ignore")
|
|
808
|
+
m = re.search(r"https://[\w.-]+\.loca\.lt\b", text)
|
|
809
|
+
return m.group(0) if m else None
|
|
810
|
+
except Exception:
|
|
811
|
+
return None
|
|
812
|
+
|
|
813
|
+
def _stop_localtunnel_processes():
|
|
814
|
+
try:
|
|
815
|
+
if IS_WINDOWS:
|
|
816
|
+
script = (
|
|
817
|
+
"Get-CimInstance Win32_Process "
|
|
818
|
+
"| Where-Object { $_.CommandLine -match 'localtunnel|\\.loca\\.lt' } "
|
|
819
|
+
"| ForEach-Object { Stop-Process -Id $_.ProcessId -Force -ErrorAction SilentlyContinue }"
|
|
820
|
+
)
|
|
821
|
+
subprocess.run(["powershell", "-NoProfile", "-Command", script], check=False)
|
|
822
|
+
else:
|
|
823
|
+
subprocess.run(["pkill", "-f", "localtunnel"], check=False)
|
|
824
|
+
except Exception:
|
|
825
|
+
pass
|
|
826
|
+
|
|
827
|
+
def _start_localtunnel(port: int, timeout: int = 25) -> str | None:
|
|
828
|
+
import time
|
|
829
|
+
npx_exec = shutil.which("npx") or shutil.which("npx.cmd")
|
|
830
|
+
if not npx_exec:
|
|
831
|
+
warn("Node.js (npx) not found. Cannot start localtunnel.")
|
|
832
|
+
return None
|
|
833
|
+
|
|
834
|
+
_stop_localtunnel_processes()
|
|
835
|
+
time.sleep(0.8)
|
|
836
|
+
|
|
837
|
+
info("Starting localtunnel in background...")
|
|
838
|
+
try:
|
|
839
|
+
LOCALTUNNEL_LOG.parent.mkdir(parents=True, exist_ok=True)
|
|
840
|
+
LOCALTUNNEL_LOG.write_text("")
|
|
841
|
+
log_handle = open(LOCALTUNNEL_LOG, "a", encoding="utf-8", buffering=1)
|
|
842
|
+
kwargs = {
|
|
843
|
+
"start_new_session": True,
|
|
844
|
+
"stdout": log_handle,
|
|
845
|
+
"stderr": subprocess.STDOUT,
|
|
846
|
+
"text": True,
|
|
847
|
+
}
|
|
848
|
+
if IS_WINDOWS and npx_exec.lower().endswith(".cmd"):
|
|
849
|
+
cmd = ["cmd", "/c", npx_exec, "-y", "localtunnel", "--port", str(port)]
|
|
850
|
+
else:
|
|
851
|
+
cmd = [npx_exec, "-y", "localtunnel", "--port", str(port)]
|
|
852
|
+
subprocess.Popen(cmd, **kwargs)
|
|
853
|
+
except Exception as e:
|
|
854
|
+
warn(f"Could not start localtunnel: {e}")
|
|
855
|
+
return None
|
|
856
|
+
|
|
857
|
+
deadline = time.time() + timeout
|
|
858
|
+
while time.time() < deadline:
|
|
859
|
+
time.sleep(1)
|
|
860
|
+
url = _find_localtunnel_url_from_log()
|
|
861
|
+
if url:
|
|
862
|
+
return url
|
|
863
|
+
return None
|
|
864
|
+
|
|
865
|
+
def _start_ngrok
|
|
866
|
+
|
|
867
|
+
(port: int) -> str | None:
|
|
737
868
|
try:
|
|
738
869
|
import pyngrok
|
|
739
870
|
from pyngrok import ngrok
|
|
@@ -1115,8 +1246,10 @@ def launch_backend():
|
|
|
1115
1246
|
token = TOKEN_FILE.read_text().strip() if TOKEN_FILE.exists() else "—"
|
|
1116
1247
|
local_ip = _detect_local_ip()
|
|
1117
1248
|
|
|
1118
|
-
#
|
|
1249
|
+
# tunnel startup
|
|
1119
1250
|
public_url: str | None = None
|
|
1251
|
+
tunnel_type: str = ""
|
|
1252
|
+
|
|
1120
1253
|
if NGROK_ENABLED.exists():
|
|
1121
1254
|
token_txt = NGROK_TOKEN.read_text().strip() if NGROK_TOKEN.exists() else ""
|
|
1122
1255
|
if not token_txt:
|
|
@@ -1125,19 +1258,30 @@ def launch_backend():
|
|
|
1125
1258
|
|
|
1126
1259
|
info("Starting ngrok in background...")
|
|
1127
1260
|
public_url = _start_ngrok(PORT)
|
|
1261
|
+
if public_url:
|
|
1262
|
+
tunnel_type = "ngrok"
|
|
1263
|
+
ok(f"ngrok active → {GREEN}{BOLD}{public_url}{RESET}")
|
|
1264
|
+
else:
|
|
1265
|
+
warn("ngrok failed. Falling back to localtunnel...")
|
|
1266
|
+
|
|
1267
|
+
if not public_url:
|
|
1268
|
+
public_url = _start_localtunnel(PORT)
|
|
1269
|
+
if public_url:
|
|
1270
|
+
tunnel_type = "localtunnel"
|
|
1271
|
+
ok(f"localtunnel active → {GREEN}{BOLD}{public_url}{RESET}")
|
|
1128
1272
|
|
|
1129
1273
|
tunnel_line = ""
|
|
1130
1274
|
tunnel_hint = ""
|
|
1131
1275
|
|
|
1132
1276
|
if public_url:
|
|
1133
|
-
tunnel_line = f" Public URL → {GREEN}{BOLD}{public_url}{RESET} {DIM}(
|
|
1277
|
+
tunnel_line = f" Public URL → {GREEN}{BOLD}{public_url}{RESET} {DIM}({tunnel_type}){RESET}"
|
|
1134
1278
|
tunnel_hint = f" · public → {GREEN}{public_url}{RESET}"
|
|
1135
|
-
ok(f"ngrok active → {GREEN}{BOLD}{public_url}{RESET}")
|
|
1136
1279
|
elif NGROK_ENABLED.exists():
|
|
1137
|
-
tunnel_line = f" Public URL → {YELLOW}(failed to start ngrok){RESET}"
|
|
1280
|
+
tunnel_line = f" Public URL → {YELLOW}(failed to start ngrok and localtunnel){RESET}"
|
|
1138
1281
|
tunnel_hint = f" · public → run manually: {DIM}ngrok http {PORT}{RESET}"
|
|
1139
1282
|
else:
|
|
1140
|
-
|
|
1283
|
+
tunnel_line = f" Public URL → {YELLOW}(failed to start localtunnel){RESET}"
|
|
1284
|
+
tunnel_hint = f" · public → configure ngrok via {DIM}python start.py --reset{RESET} or ensure node/npx is installed"
|
|
1141
1285
|
|
|
1142
1286
|
# ── Generate and display QR code ──────────────────────────────────────────
|
|
1143
1287
|
qr_url = public_url if public_url else f"http://{local_ip}:{PORT}"
|
|
@@ -1195,6 +1339,7 @@ def launch_backend_status():
|
|
|
1195
1339
|
|
|
1196
1340
|
# Fetch ngrok status via API
|
|
1197
1341
|
url = "NOT_FOUND"
|
|
1342
|
+
tunnel_type = "tunnel"
|
|
1198
1343
|
try:
|
|
1199
1344
|
import urllib.request, json
|
|
1200
1345
|
req = urllib.request.urlopen("http://127.0.0.1:4040/api/tunnels", timeout=2)
|
|
@@ -1202,12 +1347,20 @@ def launch_backend_status():
|
|
|
1202
1347
|
for tunnel in data.get("tunnels", []):
|
|
1203
1348
|
if tunnel.get("proto") == "https":
|
|
1204
1349
|
url = tunnel.get("public_url")
|
|
1350
|
+
tunnel_type = "ngrok"
|
|
1205
1351
|
break
|
|
1206
1352
|
except Exception:
|
|
1207
1353
|
pass
|
|
1208
1354
|
|
|
1209
1355
|
if url == "NOT_FOUND":
|
|
1210
|
-
|
|
1356
|
+
# Fallback to localtunnel log
|
|
1357
|
+
url_lt = _find_localtunnel_url_from_log()
|
|
1358
|
+
if url_lt:
|
|
1359
|
+
url = url_lt
|
|
1360
|
+
tunnel_type = "localtunnel"
|
|
1361
|
+
|
|
1362
|
+
if url == "NOT_FOUND":
|
|
1363
|
+
warn("Could not find a running ngrok or localtunnel URL. Is the server running?")
|
|
1211
1364
|
nl()
|
|
1212
1365
|
print(" Wait 5 seconds, or run 'superbrain-server' to start the server.")
|
|
1213
1366
|
return
|
|
@@ -1219,11 +1372,10 @@ def launch_backend_status():
|
|
|
1219
1372
|
network_url = f"http://{local_ip}:5000"
|
|
1220
1373
|
|
|
1221
1374
|
nl()
|
|
1222
|
-
print(f" Local URL
|
|
1223
|
-
print(f" Network URL
|
|
1224
|
-
print(f" Public URL
|
|
1225
|
-
print(f" API docs
|
|
1226
|
-
print(f" Access Token \u2192 {BOLD}{MAGENTA}{token}{RESET}")
|
|
1375
|
+
print(f" Local URL → {CYAN}{local_url}{RESET}")
|
|
1376
|
+
print(f" Network URL → {CYAN}{network_url}{RESET}")
|
|
1377
|
+
print(f" Public URL → {CYAN}{url}{RESET} ({tunnel_type})")
|
|
1378
|
+
print(f" API docs → {CYAN}{local_url}/docs{RESET}")
|
|
1227
1379
|
nl()
|
|
1228
1380
|
|
|
1229
1381
|
def main():
|