superbrain-server 1.0.7 → 1.0.11
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 +2 -2
- package/payload/__pycache__/api.cpython-311.pyc +0 -0
- package/payload/__pycache__/main.cpython-311.pyc +0 -0
- package/payload/__pycache__/start.cpython-311.pyc +0 -0
- package/payload/api.py +46 -4
- package/payload/core/model_router.py +12 -0
- package/payload/start.py +75 -167
- package/payload/fix.py +0 -66
- package/payload/fix2.py +0 -63
package/package.json
CHANGED
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/payload/api.py
CHANGED
|
@@ -289,7 +289,15 @@ class AnalysisResponse(BaseModel):
|
|
|
289
289
|
@app.get("/")
|
|
290
290
|
async def root():
|
|
291
291
|
"""API information and health check (no authentication required)"""
|
|
292
|
+
|
|
293
|
+
backend_id = "unknown"
|
|
294
|
+
backend_id_path = get_config_path("backend_id.txt")
|
|
295
|
+
if backend_id_path.exists():
|
|
296
|
+
backend_id = backend_id_path.read_text().strip()
|
|
297
|
+
|
|
292
298
|
return {
|
|
299
|
+
"backendId": backend_id,
|
|
300
|
+
|
|
293
301
|
"name": "SuperBrain Instagram Analyzer API",
|
|
294
302
|
"version": "1.02",
|
|
295
303
|
"status": "operational",
|
|
@@ -561,12 +569,46 @@ async def analyze_instagram(request: AnalyzeRequest, token: str = Depends(verify
|
|
|
561
569
|
logger.warning(f"⚠️ [{shortcode}] main.py stderr:\n{stderr[:1000]}")
|
|
562
570
|
|
|
563
571
|
if returncode == 2:
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
572
|
+
# main.py detected quota exhaustion and queued item for retry.
|
|
573
|
+
# NOTE: Do NOT remove from queue here — main.py already called
|
|
574
|
+
# queue_for_retry() which set status='retry'. Removing would lose it.
|
|
575
|
+
logger.info(f"⏰ [{shortcode}] Quota exhausted — queued for automatic retry")
|
|
567
576
|
raise HTTPException(
|
|
568
577
|
status_code=202,
|
|
569
|
-
detail=
|
|
578
|
+
detail="API quota exhausted. Your request has been queued for automatic retry in 24 hours."
|
|
579
|
+
)
|
|
580
|
+
|
|
581
|
+
if returncode != 0:
|
|
582
|
+
# Extract last meaningful error line from stdout for the error message
|
|
583
|
+
error_lines = [l.strip() for l in stdout.splitlines() if l.strip() and ('❌' in l or 'Error' in l or 'failed' in l.lower())]
|
|
584
|
+
error_detail = error_lines[-1] if error_lines else (stderr.strip()[:200] or "Analysis failed")
|
|
585
|
+
logger.error(f"❌ [{shortcode}] Analysis failed: {error_detail}")
|
|
586
|
+
logger.debug(f"[{shortcode}] stdout tail:\n{stdout[-800:]}")
|
|
587
|
+
raise HTTPException(
|
|
588
|
+
status_code=400,
|
|
589
|
+
detail=error_detail
|
|
590
|
+
)
|
|
591
|
+
|
|
592
|
+
logger.info(f"✅ [{shortcode}] Analysis complete! Fetching from database...")
|
|
593
|
+
|
|
594
|
+
# Get result from database — retry up to 4 times in case the SQLite write
|
|
595
|
+
# hasn't flushed yet (race condition between subprocess write and our read).
|
|
596
|
+
analysis = None
|
|
597
|
+
for _attempt in range(4):
|
|
598
|
+
analysis = db.check_cache(shortcode)
|
|
599
|
+
if analysis:
|
|
600
|
+
if _attempt > 0:
|
|
601
|
+
logger.info(f"🔄 [{shortcode}] Found in database on retry {_attempt}")
|
|
602
|
+
break
|
|
603
|
+
if _attempt < 3:
|
|
604
|
+
logger.warning(f"⏳ [{shortcode}] Not in DB yet (attempt {_attempt+1}/4), retrying in 1s…")
|
|
605
|
+
await asyncio.sleep(1)
|
|
606
|
+
|
|
607
|
+
if not analysis:
|
|
608
|
+
logger.error(f"❌ [{shortcode}] Not found in database after 4 attempts!")
|
|
609
|
+
raise HTTPException(
|
|
610
|
+
status_code=500,
|
|
611
|
+
detail="Analysis completed but result not found in database"
|
|
570
612
|
)
|
|
571
613
|
|
|
572
614
|
# Filter response
|
|
@@ -663,6 +663,9 @@ class ModelRouter:
|
|
|
663
663
|
try:
|
|
664
664
|
self._refresh_openrouter_models()
|
|
665
665
|
except Exception as e:
|
|
666
|
+
if "429" in str(e) or "quota" in str(e).lower():
|
|
667
|
+
raise RateLimitError("Quota limit hit")
|
|
668
|
+
raise e
|
|
666
669
|
print(f"⚠️ OpenRouter auto-refresh error: {e}")
|
|
667
670
|
time.sleep(OPENROUTER_FREE_CACHE_HOURS * 3600)
|
|
668
671
|
|
|
@@ -707,6 +710,9 @@ class ModelRouter:
|
|
|
707
710
|
resp.raise_for_status()
|
|
708
711
|
all_models = resp.json().get("data", [])
|
|
709
712
|
except Exception as e:
|
|
713
|
+
if "429" in str(e) or "quota" in str(e).lower():
|
|
714
|
+
raise RateLimitError("Quota limit hit")
|
|
715
|
+
raise e
|
|
710
716
|
print(f"⚠️ OpenRouter model discovery failed: {e}")
|
|
711
717
|
return
|
|
712
718
|
|
|
@@ -1100,6 +1106,9 @@ class ModelRouter:
|
|
|
1100
1106
|
return result
|
|
1101
1107
|
|
|
1102
1108
|
except Exception as e:
|
|
1109
|
+
if "429" in str(e) or "quota" in str(e).lower():
|
|
1110
|
+
raise RateLimitError("Quota limit hit")
|
|
1111
|
+
raise e
|
|
1103
1112
|
status = 429 if "429" in str(e) else 0
|
|
1104
1113
|
self._record_failure(key, str(e), status_code=status)
|
|
1105
1114
|
print(f" ✗ Failed ({type(e).__name__}), trying next …", flush=True)
|
|
@@ -1144,6 +1153,9 @@ class ModelRouter:
|
|
|
1144
1153
|
return result
|
|
1145
1154
|
|
|
1146
1155
|
except Exception as e:
|
|
1156
|
+
if "429" in str(e) or "quota" in str(e).lower():
|
|
1157
|
+
raise RateLimitError("Quota limit hit")
|
|
1158
|
+
raise e
|
|
1147
1159
|
status = 429 if "429" in str(e) else 0
|
|
1148
1160
|
self._record_failure(key, str(e), status_code=status)
|
|
1149
1161
|
print(f" ✗ Failed ({type(e).__name__}), trying next …", flush=True)
|
package/payload/start.py
CHANGED
|
@@ -22,6 +22,7 @@ import string
|
|
|
22
22
|
import textwrap
|
|
23
23
|
import time
|
|
24
24
|
import importlib
|
|
25
|
+
import re
|
|
25
26
|
from pathlib import Path
|
|
26
27
|
|
|
27
28
|
# ── Paths ─────────────────────────────────────────────────────────────────────
|
|
@@ -157,6 +158,7 @@ def ensure_runtime_dependencies():
|
|
|
157
158
|
("fastapi", "fastapi"),
|
|
158
159
|
("uvicorn", "uvicorn"),
|
|
159
160
|
("multipart", "python-multipart"),
|
|
161
|
+
("pyngrok", "pyngrok"),
|
|
160
162
|
("instaloader", "instaloader"),
|
|
161
163
|
("segno", "segno"),
|
|
162
164
|
]
|
|
@@ -662,64 +664,41 @@ def setup_whisper():
|
|
|
662
664
|
# ══════════════════════════════════════════════════════════════════════════════
|
|
663
665
|
# Step 6 — Remote Access / Port Forwarding
|
|
664
666
|
# ══════════════════════════════════════════════════════════════════════════════
|
|
665
|
-
|
|
666
|
-
|
|
667
|
+
NGROK_ENABLED = BASE_DIR / "config" / "ngrok_enabled.txt"
|
|
668
|
+
NGROK_TOKEN = BASE_DIR / "config" / "ngrok_token.txt"
|
|
667
669
|
|
|
668
670
|
def setup_remote_access():
|
|
669
|
-
h1("Step 6 of 7 — Remote Access (
|
|
671
|
+
h1("Step 6 of 7 — Remote Access (ngrok)")
|
|
670
672
|
|
|
671
673
|
print(f"""
|
|
672
|
-
The SuperBrain backend runs
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
{BOLD}Option A — localhost.run (easiest + free ssh tunnel){RESET}
|
|
678
|
-
localhost.run creates a public HTTPS URL seamlessly using SSH.
|
|
679
|
-
No account required.
|
|
680
|
-
Official site: {CYAN}https://localhost.run/{RESET}
|
|
681
|
-
|
|
682
|
-
{BOLD}Option B — Your own port forwarding (advanced){RESET}
|
|
683
|
-
Forward {BOLD}TCP port 5000{RESET} on your router to your machine's local IP.
|
|
684
|
-
Then use {BOLD}http://<your-public-ip>:5000{RESET} in the mobile app.
|
|
685
|
-
Steps:
|
|
686
|
-
1. Find your machine's local IP → ip addr (Linux) / ipconfig (Windows)
|
|
687
|
-
2. Log into your router admin panel (usually http://192.168.1.1)
|
|
688
|
-
3. Add a port forwarding rule: External 5000 → Internal <your-local-IP>:5000
|
|
689
|
-
4. Use your public IP (check https://ipify.org) in the mobile app.
|
|
690
|
-
{YELLOW}Note: dynamic public IPs change on router restart — consider a DDNS service.{RESET}
|
|
691
|
-
|
|
692
|
-
{DIM}You can also run only on your local WiFi — both phone and PC must be on
|
|
693
|
-
the same network. Use your PC's local IP (e.g. 192.168.x.x) in the app.{RESET}
|
|
674
|
+
The SuperBrain backend runs locally. Your phone needs to reach it over the internet.
|
|
675
|
+
We recommend {BOLD}ngrok{RESET} for a secure tunnel.
|
|
676
|
+
|
|
677
|
+
Requires a free account from: {CYAN}https://dashboard.ngrok.com/signup{RESET}
|
|
678
|
+
Get your Authtoken at: {CYAN}https://dashboard.ngrok.com/get-started/your-authtoken{RESET}
|
|
694
679
|
""")
|
|
695
680
|
|
|
696
|
-
choice = ask_yn("Enable
|
|
681
|
+
choice = ask_yn("Enable ngrok on startup?", default=True)
|
|
697
682
|
if not choice:
|
|
698
|
-
|
|
699
|
-
warn("Skipping
|
|
700
|
-
info("Remember: set the correct server URL in the mobile app Settings.")
|
|
683
|
+
NGROK_ENABLED.unlink(missing_ok=True)
|
|
684
|
+
warn("Skipping ngrok. Local WiFi only.")
|
|
701
685
|
return
|
|
702
686
|
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
LOCALTUNNEL_ENABLED.parent.mkdir(parents=True, exist_ok=True)
|
|
719
|
-
LOCALTUNNEL_ENABLED.write_text("enabled")
|
|
720
|
-
ok("localhost.run auto-start enabled")
|
|
721
|
-
nl()
|
|
722
|
-
info("localhost.run will be started automatically every time you run start.py.")
|
|
687
|
+
ok("ngrok auto-start enabled")
|
|
688
|
+
NGROK_ENABLED.parent.mkdir(parents=True, exist_ok=True)
|
|
689
|
+
NGROK_ENABLED.write_text("enabled")
|
|
690
|
+
|
|
691
|
+
existing_token = NGROK_TOKEN.read_text().strip() if NGROK_TOKEN.exists() else ""
|
|
692
|
+
print(f"\n {YELLOW}Please paste your ngrok Authtoken.{RESET}")
|
|
693
|
+
if existing_token:
|
|
694
|
+
print(f" {DIM}(Leave blank to keep existing token){RESET}")
|
|
695
|
+
|
|
696
|
+
auth_token = ask("Authtoken", default=existing_token, paste=True)
|
|
697
|
+
if auth_token.strip():
|
|
698
|
+
NGROK_TOKEN.write_text(auth_token.strip())
|
|
699
|
+
ok("ngrok token saved.")
|
|
700
|
+
else:
|
|
701
|
+
warn("No ngrok token provided. ngrok may disconnect. To fix, re-run setup.")
|
|
723
702
|
|
|
724
703
|
# ══════════════════════════════════════════════════════════════════════════════
|
|
725
704
|
# Step 6 — Access Token & Database
|
|
@@ -754,99 +733,21 @@ def setup_token_and_db():
|
|
|
754
733
|
# ══════════════════════════════════════════════════════════════════════════════
|
|
755
734
|
# Launch Backend
|
|
756
735
|
# ══════════════════════════════════════════════════════════════════════════════
|
|
757
|
-
def
|
|
758
|
-
"""Extract first localhost.run public URL from text."""
|
|
759
|
-
import re
|
|
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
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
def _find_localhost_run_url_from_log() -> str | None:
|
|
770
|
-
"""Read local tunnel log and return detected public URL if available."""
|
|
771
|
-
try:
|
|
772
|
-
if not LOCALTUNNEL_LOG.exists():
|
|
773
|
-
return None
|
|
774
|
-
text = LOCALTUNNEL_LOG.read_text(encoding="utf-8", errors="ignore")
|
|
775
|
-
return _extract_localhost_run_url(text)
|
|
776
|
-
except Exception:
|
|
777
|
-
return None
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
def _stop_localhost_run_processes():
|
|
781
|
-
"""Stop existing localhost.run processes so only one tunnel remains active."""
|
|
782
|
-
try:
|
|
783
|
-
if IS_WINDOWS:
|
|
784
|
-
script = (
|
|
785
|
-
"Get-CimInstance Win32_Process "
|
|
786
|
-
"| Where-Object { $_.CommandLine -match 'nokey@localhost.run' } "
|
|
787
|
-
"| ForEach-Object { Stop-Process -Id $_.ProcessId -Force -ErrorAction SilentlyContinue }"
|
|
788
|
-
)
|
|
789
|
-
subprocess.run(["powershell", "-NoProfile", "-Command", script], check=False)
|
|
790
|
-
else:
|
|
791
|
-
subprocess.run(["pkill", "-f", "nokey@localhost.run"], check=False)
|
|
792
|
-
except Exception:
|
|
793
|
-
pass
|
|
794
|
-
|
|
795
|
-
|
|
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."""
|
|
798
|
-
import time as _time
|
|
799
|
-
|
|
800
|
-
ssh_exec = shutil.which("ssh")
|
|
801
|
-
if not ssh_exec:
|
|
802
|
-
warn("SSH is required to use localhost.run. Please install OpenSSH.")
|
|
803
|
-
return None
|
|
804
|
-
|
|
805
|
-
# Clean stale ssh tunnel processes.
|
|
806
|
-
_stop_localhost_run_processes()
|
|
807
|
-
_time.sleep(0.8)
|
|
808
|
-
|
|
809
|
-
info("Starting localhost.run SSH tunnel in background …")
|
|
736
|
+
def _start_ngrok(port: int) -> str | None:
|
|
810
737
|
try:
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
log_handle = open(LOCALTUNNEL_LOG, "a", encoding="utf-8", buffering=1)
|
|
815
|
-
kwargs = {
|
|
816
|
-
"stdout": log_handle,
|
|
817
|
-
"stderr": subprocess.STDOUT,
|
|
818
|
-
"stdin": subprocess.DEVNULL,
|
|
819
|
-
"text": True,
|
|
820
|
-
}
|
|
821
|
-
if IS_WINDOWS:
|
|
822
|
-
kwargs["creationflags"] = subprocess.CREATE_NEW_PROCESS_GROUP
|
|
738
|
+
import pyngrok
|
|
739
|
+
from pyngrok import ngrok
|
|
823
740
|
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
return None
|
|
831
|
-
|
|
741
|
+
token = NGROK_TOKEN.read_text().strip() if NGROK_TOKEN.exists() else None
|
|
742
|
+
if token:
|
|
743
|
+
ngrok.set_auth_token(token)
|
|
744
|
+
|
|
745
|
+
tunnel = ngrok.connect(port, bind_tls=True)
|
|
746
|
+
return tunnel.public_url
|
|
832
747
|
except Exception as e:
|
|
833
|
-
warn(f"
|
|
748
|
+
warn(f"Failed to start ngrok: {e}")
|
|
834
749
|
return None
|
|
835
750
|
|
|
836
|
-
# Poll log output until URL is emitted.
|
|
837
|
-
deadline = _time.time() + timeout
|
|
838
|
-
while _time.time() < deadline:
|
|
839
|
-
_time.sleep(1)
|
|
840
|
-
url = _find_localhost_run_url_from_log()
|
|
841
|
-
if url:
|
|
842
|
-
ok(f"localhost.run active → {GREEN}{BOLD}{url}{RESET}")
|
|
843
|
-
return url
|
|
844
|
-
|
|
845
|
-
warn("localhost.run started but URL is not available yet.")
|
|
846
|
-
info(f"Check tunnel logs in: {LOCALTUNNEL_LOG}")
|
|
847
|
-
return None
|
|
848
|
-
|
|
849
|
-
|
|
850
751
|
def _get_windows_pids_on_port(port: int) -> list[int]:
|
|
851
752
|
"""Return listener PIDs on Windows using Get-NetTCPConnection when available."""
|
|
852
753
|
pids: set[int] = set()
|
|
@@ -1214,26 +1115,27 @@ def launch_backend():
|
|
|
1214
1115
|
token = TOKEN_FILE.read_text().strip() if TOKEN_FILE.exists() else "—"
|
|
1215
1116
|
local_ip = _detect_local_ip()
|
|
1216
1117
|
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1118
|
+
# ngrok startup
|
|
1119
|
+
public_url: str | None = None
|
|
1120
|
+
if NGROK_ENABLED.exists():
|
|
1121
|
+
info("Starting ngrok in background...")
|
|
1122
|
+
public_url = _start_ngrok(PORT)
|
|
1123
|
+
|
|
1124
|
+
tunnel_line = ""
|
|
1125
|
+
tunnel_hint = ""
|
|
1126
|
+
|
|
1127
|
+
if public_url:
|
|
1128
|
+
tunnel_line = f" Public URL → {GREEN}{BOLD}{public_url}{RESET} {DIM}(ngrok){RESET}"
|
|
1129
|
+
tunnel_hint = f" · public → {GREEN}{public_url}{RESET}"
|
|
1130
|
+
ok(f"ngrok active → {GREEN}{BOLD}{public_url}{RESET}")
|
|
1131
|
+
elif NGROK_ENABLED.exists():
|
|
1132
|
+
tunnel_line = f" Public URL → {YELLOW}(failed to start ngrok){RESET}"
|
|
1133
|
+
tunnel_hint = f" · public → run manually: {DIM}ngrok http {PORT}{RESET}"
|
|
1231
1134
|
else:
|
|
1232
|
-
|
|
1233
|
-
tunnel_hint = f" · public → install OpenSSH first, then run: {DIM}ssh -R 80:localhost:{PORT} nokey@localhost.run{RESET}"
|
|
1135
|
+
tunnel_hint = f" · public → enable ngrok via {DIM}python start.py --reset{RESET}"
|
|
1234
1136
|
|
|
1235
1137
|
# ── Generate and display QR code ──────────────────────────────────────────
|
|
1236
|
-
qr_url =
|
|
1138
|
+
qr_url = public_url if public_url else f"http://{local_ip}:{PORT}"
|
|
1237
1139
|
_display_connect_qr(qr_url, token)
|
|
1238
1140
|
|
|
1239
1141
|
print(f"""
|
|
@@ -1286,15 +1188,21 @@ def launch_backend_status():
|
|
|
1286
1188
|
if TOKEN_FILE.exists():
|
|
1287
1189
|
token = TOKEN_FILE.read_text(encoding="utf-8").strip()
|
|
1288
1190
|
|
|
1191
|
+
# Fetch ngrok status via API
|
|
1289
1192
|
url = "NOT_FOUND"
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1193
|
+
try:
|
|
1194
|
+
import urllib.request, json
|
|
1195
|
+
req = urllib.request.urlopen("http://127.0.0.1:4040/api/tunnels", timeout=2)
|
|
1196
|
+
data = json.loads(req.read())
|
|
1197
|
+
for tunnel in data.get("tunnels", []):
|
|
1198
|
+
if tunnel.get("proto") == "https":
|
|
1199
|
+
url = tunnel.get("public_url")
|
|
1200
|
+
break
|
|
1201
|
+
except Exception:
|
|
1202
|
+
pass
|
|
1203
|
+
|
|
1296
1204
|
if url == "NOT_FOUND":
|
|
1297
|
-
warn("Could not find a running
|
|
1205
|
+
warn("Could not find a running ngrok URL. Is the server running?")
|
|
1298
1206
|
nl()
|
|
1299
1207
|
print(" Wait 5 seconds, or run 'superbrain-server' to start the server.")
|
|
1300
1208
|
return
|
|
@@ -1306,11 +1214,11 @@ def launch_backend_status():
|
|
|
1306
1214
|
network_url = f"http://{local_ip}:5000"
|
|
1307
1215
|
|
|
1308
1216
|
nl()
|
|
1309
|
-
print(f" Local URL
|
|
1310
|
-
print(f" Network URL
|
|
1311
|
-
print(f" Public URL
|
|
1312
|
-
print(f" API docs
|
|
1313
|
-
print(f" Access Token
|
|
1217
|
+
print(f" Local URL \u2192 {CYAN}{local_url}{RESET}")
|
|
1218
|
+
print(f" Network URL \u2192 {CYAN}{network_url}{RESET}")
|
|
1219
|
+
print(f" Public URL \u2192 {CYAN}{url}{RESET} (localtunnel)")
|
|
1220
|
+
print(f" API docs \u2192 {CYAN}{local_url}/docs{RESET}")
|
|
1221
|
+
print(f" Access Token \u2192 {BOLD}{MAGENTA}{token}{RESET}")
|
|
1314
1222
|
nl()
|
|
1315
1223
|
|
|
1316
1224
|
def main():
|
|
@@ -1339,7 +1247,7 @@ def main():
|
|
|
1339
1247
|
3 · Configure AI provider keys + Instagram credentials
|
|
1340
1248
|
4 · Set up an offline AI model via Ollama (qwen3-vl:4b)
|
|
1341
1249
|
5 · Set up offline audio transcription (Whisper + ffmpeg)
|
|
1342
|
-
6 · Configure remote access (
|
|
1250
|
+
6 · Configure remote access (localtunnel or port forwarding)
|
|
1343
1251
|
7 · Generate Access Token & initialise database
|
|
1344
1252
|
|
|
1345
1253
|
Press {BOLD}Enter{RESET} to accept defaults shown in [{DIM}brackets{RESET}].
|
package/payload/fix.py
DELETED
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
import sys, shutil
|
|
2
|
-
path1 = 'D:/superbrain/backend/start.py'
|
|
3
|
-
path2 = 'D:/superbrain/superbrain-cli/payload/start.py'
|
|
4
|
-
|
|
5
|
-
status_code = '''
|
|
6
|
-
def launch_backend_status():
|
|
7
|
-
h1("SuperBrain Server Status")
|
|
8
|
-
PORT = 5000
|
|
9
|
-
token = TOKEN_FILE.read_text().strip() if TOKEN_FILE.exists() else "�"
|
|
10
|
-
local_ip = _detect_local_ip()
|
|
11
|
-
|
|
12
|
-
localtunnel_enabled = bool(shutil.which("npx") or shutil.which("npx.cmd"))
|
|
13
|
-
localtunnel_url: str | None = None
|
|
14
|
-
if localtunnel_enabled:
|
|
15
|
-
localtunnel_url = _find_localtunnel_url_from_log()
|
|
16
|
-
|
|
17
|
-
if localtunnel_url:
|
|
18
|
-
tunnel_line = f" Public URL ? {GREEN}{BOLD}{localtunnel_url}{RESET} {DIM}(localtunnel){RESET}"
|
|
19
|
-
tunnel_hint = f" � public ? {GREEN}{localtunnel_url}{RESET}"
|
|
20
|
-
elif localtunnel_enabled:
|
|
21
|
-
tunnel_line = f" Public URL ? {YELLOW}(running � URL in localtunnel.log){RESET}"
|
|
22
|
-
tunnel_hint = f" � public ? run: {DIM}npx localtunnel --port {PORT}{RESET}"
|
|
23
|
-
else:
|
|
24
|
-
tunnel_line = ""
|
|
25
|
-
tunnel_hint = f" � public ? install Node.js first, then run: {DIM}npx localtunnel --port {PORT}{RESET}"
|
|
26
|
-
|
|
27
|
-
qr_url = localtunnel_url if localtunnel_url else f"http://{local_ip}:{PORT}"
|
|
28
|
-
_display_connect_qr(qr_url, token)
|
|
29
|
-
|
|
30
|
-
print(f\"\"\"
|
|
31
|
-
{GREEN}{BOLD}Server Status{RESET}
|
|
32
|
-
|
|
33
|
-
Local URL ? {CYAN}http://127.0.0.1:{PORT}{RESET}
|
|
34
|
-
Network URL ? {CYAN}http://{local_ip}:{PORT}{RESET}
|
|
35
|
-
{(tunnel_line + chr(10)) if tunnel_line else ''} API docs ? {CYAN}http://127.0.0.1:{PORT}/docs{RESET}
|
|
36
|
-
Access Token ? {BOLD}{MAGENTA}{token}{RESET}
|
|
37
|
-
|
|
38
|
-
{YELLOW}Mobile app setup:{RESET}
|
|
39
|
-
{BOLD}Option A � Scan QR code:{RESET}
|
|
40
|
-
1. Open the app ? Settings ? tap the {BOLD}QR icon{RESET} ??
|
|
41
|
-
2. Scan the QR code shown above
|
|
42
|
-
|
|
43
|
-
{BOLD}Option B � Manual setup:{RESET}
|
|
44
|
-
1. Go to app ? ? settings
|
|
45
|
-
2. Set {BOLD}Server URL{RESET} to:
|
|
46
|
-
{tunnel_hint}
|
|
47
|
-
� Same WiFi ? http://{local_ip}:{PORT}
|
|
48
|
-
3. Set {BOLD}Access Token{RESET} to: {BOLD}{MAGENTA}{token}{RESET}
|
|
49
|
-
\"\"\")
|
|
50
|
-
sys.exit(0)
|
|
51
|
-
|
|
52
|
-
'''
|
|
53
|
-
|
|
54
|
-
for path in [path1, path2]:
|
|
55
|
-
with open(path, 'r', encoding='utf-8') as f:
|
|
56
|
-
content = f.read()
|
|
57
|
-
|
|
58
|
-
if 'def launch_backend_status()' not in content:
|
|
59
|
-
content = content.replace('def main():', status_code + 'def main():')
|
|
60
|
-
|
|
61
|
-
if 'status_mode = "--status" in sys.argv' not in content:
|
|
62
|
-
content = content.replace(' reset_mode = "--reset" in sys.argv', ' status_mode = "--status" in sys.argv\\n if status_mode:\\n launch_backend_status()\\n return\\n\\n reset_mode = "--reset" in sys.argv')
|
|
63
|
-
|
|
64
|
-
with open(path, 'w', encoding='utf-8') as f:
|
|
65
|
-
f.write(content)
|
|
66
|
-
print("Done fixing start.py!")
|
package/payload/fix2.py
DELETED
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
import sys, shutil
|
|
2
|
-
path1 = 'D:/superbrain/backend/start.py'
|
|
3
|
-
path2 = 'D:/superbrain/superbrain-cli/payload/start.py'
|
|
4
|
-
|
|
5
|
-
status_code = '''
|
|
6
|
-
def launch_backend_status():
|
|
7
|
-
h1("SuperBrain Server Status")
|
|
8
|
-
PORT = 5000
|
|
9
|
-
token = TOKEN_FILE.read_text().strip() if TOKEN_FILE.exists() else "�"
|
|
10
|
-
local_ip = _detect_local_ip()
|
|
11
|
-
|
|
12
|
-
localtunnel_enabled = bool(shutil.which("npx") or shutil.which("npx.cmd"))
|
|
13
|
-
localtunnel_url: str | None = None
|
|
14
|
-
if localtunnel_enabled:
|
|
15
|
-
localtunnel_url = _find_localtunnel_url_from_log()
|
|
16
|
-
|
|
17
|
-
if localtunnel_url:
|
|
18
|
-
tunnel_line = f" Public URL ? {GREEN}{BOLD}{localtunnel_url}{RESET} {DIM}(localtunnel){RESET}"
|
|
19
|
-
tunnel_hint = f" � public ? {GREEN}{localtunnel_url}{RESET}"
|
|
20
|
-
elif localtunnel_enabled:
|
|
21
|
-
tunnel_line = f" Public URL ? {YELLOW}(running � URL in localtunnel.log){RESET}"
|
|
22
|
-
tunnel_hint = f" � public ? run: {DIM}npx localtunnel --port {PORT}{RESET}"
|
|
23
|
-
else:
|
|
24
|
-
tunnel_line = ""
|
|
25
|
-
tunnel_hint = f" � public ? install Node.js first, then run: {DIM}npx localtunnel --port {PORT}{RESET}"
|
|
26
|
-
|
|
27
|
-
qr_url = localtunnel_url if localtunnel_url else f"http://{local_ip}:{PORT}"
|
|
28
|
-
_display_connect_qr(qr_url, token)
|
|
29
|
-
|
|
30
|
-
print(f\"\"\"
|
|
31
|
-
{GREEN}{BOLD}Server Status{RESET}
|
|
32
|
-
|
|
33
|
-
Local URL ? {CYAN}http://127.0.0.1:{PORT}{RESET}
|
|
34
|
-
Network URL ? {CYAN}http://{local_ip}:{PORT}{RESET}
|
|
35
|
-
{(tunnel_line + chr(10)) if tunnel_line else ''} API docs ? {CYAN}http://127.0.0.1:{PORT}/docs{RESET}
|
|
36
|
-
Access Token ? {BOLD}{MAGENTA}{token}{RESET}
|
|
37
|
-
|
|
38
|
-
{YELLOW}Mobile app setup:{RESET}
|
|
39
|
-
{BOLD}Option A � Scan QR code:{RESET}
|
|
40
|
-
1. Open the app ? Settings ? tap the {BOLD}QR icon{RESET} ??
|
|
41
|
-
2. Scan the QR code shown above
|
|
42
|
-
|
|
43
|
-
{BOLD}Option B � Manual setup:{RESET}
|
|
44
|
-
1. Go to app ? ? settings
|
|
45
|
-
2. Set {BOLD}Server URL{RESET} to:
|
|
46
|
-
{tunnel_hint}
|
|
47
|
-
� Same WiFi ? http://{local_ip}:{PORT}
|
|
48
|
-
3. Set {BOLD}Access Token{RESET} to: {BOLD}{MAGENTA}{token}{RESET}
|
|
49
|
-
\"\"\")
|
|
50
|
-
sys.exit(0)
|
|
51
|
-
|
|
52
|
-
'''
|
|
53
|
-
|
|
54
|
-
for path in [path1, path2]:
|
|
55
|
-
with open(path, 'r', encoding='utf-8') as f:
|
|
56
|
-
content = f.read()
|
|
57
|
-
|
|
58
|
-
if 'def launch_backend_status()' not in content:
|
|
59
|
-
content = content.replace('def main():', status_code + 'def main():')
|
|
60
|
-
|
|
61
|
-
with open(path, 'w', encoding='utf-8') as f:
|
|
62
|
-
f.write(content)
|
|
63
|
-
print("Done fixing start.py again!")
|