superbrain-server 1.0.7 → 1.0.10
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/analyzers/__pycache__/__init__.cpython-311.pyc +0 -0
- package/payload/analyzers/__pycache__/audio_transcribe.cpython-311.pyc +0 -0
- package/payload/analyzers/__pycache__/caption.cpython-311.pyc +0 -0
- package/payload/analyzers/__pycache__/music_identifier.cpython-311.pyc +0 -0
- package/payload/analyzers/__pycache__/text_analyzer.cpython-311.pyc +0 -0
- package/payload/analyzers/__pycache__/visual_analyze.cpython-311.pyc +0 -0
- package/payload/analyzers/__pycache__/webpage_analyzer.cpython-311.pyc +0 -0
- package/payload/analyzers/__pycache__/youtube_analyzer.cpython-311.pyc +0 -0
- package/payload/api.py +46 -4
- package/payload/config/backend_id.txt +1 -0
- package/payload/config/localtunnel.log +86 -0
- package/payload/core/__pycache__/__init__.cpython-311.pyc +0 -0
- package/payload/core/__pycache__/category_manager.cpython-311.pyc +0 -0
- package/payload/core/__pycache__/database.cpython-311.pyc +0 -0
- package/payload/core/__pycache__/link_checker.cpython-311.pyc +0 -0
- package/payload/core/__pycache__/model_router.cpython-311.pyc +0 -0
- package/payload/core/model_router.py +12 -0
- package/payload/instagram/__pycache__/__init__.cpython-311.pyc +0 -0
- package/payload/instagram/__pycache__/instagram_downloader.cpython-311.pyc +0 -0
- package/payload/instagram/__pycache__/instagram_login.cpython-311.pyc +0 -0
- package/payload/start.py +55 -67
- package/payload/test_backend.py +241 -0
- package/payload/tests/__init__.py +0 -0
- package/payload/tests/__pycache__/__init__.cpython-311.pyc +0 -0
- package/payload/tests/__pycache__/test_api.cpython-311.pyc +0 -0
- package/payload/tests/__pycache__/test_db.cpython-311.pyc +0 -0
- package/payload/tests/__pycache__/test_sync_code.cpython-311.pyc +0 -0
- package/payload/tests/test_api.py +17 -0
- package/payload/tests/test_db.py +22 -0
- package/payload/tests/test_sync_code.py +65 -0
- package/payload/utils/__pycache__/__init__.cpython-311.pyc +0 -0
- package/payload/utils/__pycache__/db_stats.cpython-311.pyc +0 -0
- package/payload/utils/__pycache__/manage_token.cpython-311.pyc +0 -0
- package/payload/fix.py +0 -66
- package/payload/fix2.py +0 -63
package/package.json
CHANGED
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
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
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
3f491f84-184a-40ba-9660-b882b881c4d5
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
|
|
2
|
+
===============================================================================
|
|
3
|
+
Welcome to localhost.run!
|
|
4
|
+
|
|
5
|
+
Follow your favourite reverse tunnel at [https://twitter.com/localhost_run].
|
|
6
|
+
|
|
7
|
+
To set up and manage custom domains go to https://admin.localhost.run/
|
|
8
|
+
|
|
9
|
+
More details on custom domains (and how to enable subdomains of your custom
|
|
10
|
+
domain) at https://localhost.run/docs/custom-domains
|
|
11
|
+
|
|
12
|
+
If you get a permission denied error check the faq for how to connect with a key or
|
|
13
|
+
create a free tunnel without a key at [http://localhost:3000/docs/faq#generating-an-ssh-key].
|
|
14
|
+
|
|
15
|
+
To explore using localhost.run visit the documentation site:
|
|
16
|
+
https://localhost.run/docs/
|
|
17
|
+
|
|
18
|
+
===============================================================================
|
|
19
|
+
|
|
20
|
+
** your connection id is 78ac0b8b-a8f4-4185-9da5-a446d15e032f, please mention it if you send me a message about an issue. **
|
|
21
|
+
|
|
22
|
+
authenticated as anonymous user
|
|
23
|
+
14ebb2ac2ccab3.lhr.life tunneled with tls termination, https://14ebb2ac2ccab3.lhr.life
|
|
24
|
+
create an account and add your key for a longer lasting domain name. see https://localhost.run/docs/forever-free/ for more information.
|
|
25
|
+
Open your tunnel address on your mobile with this QR:
|
|
26
|
+
|
|
27
|
+
[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m
|
|
28
|
+
[7m [0m[49m [0m[49m [0m[49m [0m[49m [0m[49m [0m[49m [0m[49m [0m[7m [0m[49m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[49m [0m[49m [0m[49m [0m[49m [0m[49m [0m[49m [0m[49m [0m[7m [0m
|
|
29
|
+
[7m [0m[49m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[49m [0m[7m [0m[49m [0m[7m [0m[7m [0m[49m [0m[49m [0m[49m [0m[49m [0m[7m [0m[49m [0m[7m [0m[49m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[49m [0m[7m [0m
|
|
30
|
+
[7m [0m[49m [0m[7m [0m[49m [0m[49m [0m[49m [0m[7m [0m[49m [0m[7m [0m[49m [0m[49m [0m[7m [0m[7m [0m[49m [0m[49m [0m[7m [0m[7m [0m[49m [0m[7m [0m[49m [0m[7m [0m[49m [0m[49m [0m[49m [0m[7m [0m[49m [0m[7m [0m
|
|
31
|
+
[7m [0m[49m [0m[7m [0m[49m [0m[49m [0m[49m [0m[7m [0m[49m [0m[7m [0m[49m [0m[49m [0m[49m [0m[7m [0m[49m [0m[49m [0m[49m [0m[7m [0m[7m [0m[7m [0m[49m [0m[7m [0m[49m [0m[49m [0m[49m [0m[7m [0m[49m [0m[7m [0m
|
|
32
|
+
[7m [0m[49m [0m[7m [0m[49m [0m[49m [0m[49m [0m[7m [0m[49m [0m[7m [0m[7m [0m[49m [0m[49m [0m[7m [0m[49m [0m[7m [0m[49m [0m[49m [0m[7m [0m[7m [0m[49m [0m[7m [0m[49m [0m[49m [0m[49m [0m[7m [0m[49m [0m[7m [0m
|
|
33
|
+
[7m [0m[49m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[49m [0m[7m [0m[49m [0m[49m [0m[49m [0m[7m [0m[7m [0m[7m [0m[7m [0m[49m [0m[49m [0m[7m [0m[49m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[49m [0m[7m [0m
|
|
34
|
+
[7m [0m[49m [0m[49m [0m[49m [0m[49m [0m[49m [0m[49m [0m[49m [0m[7m [0m[49m [0m[7m [0m[49m [0m[7m [0m[49m [0m[7m [0m[49m [0m[7m [0m[49m [0m[7m [0m[49m [0m[49m [0m[49m [0m[49m [0m[49m [0m[49m [0m[49m [0m[7m [0m
|
|
35
|
+
[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[49m [0m[49m [0m[7m [0m[49m [0m[7m [0m[7m [0m[49m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m
|
|
36
|
+
[7m [0m[49m [0m[49m [0m[7m [0m[7m [0m[49m [0m[49m [0m[49m [0m[7m [0m[7m [0m[7m [0m[49m [0m[7m [0m[49m [0m[7m [0m[7m [0m[49m [0m[49m [0m[7m [0m[7m [0m[49m [0m[7m [0m[49m [0m[49m [0m[49m [0m[49m [0m[7m [0m
|
|
37
|
+
[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[49m [0m[7m [0m[7m [0m[49m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[49m [0m[49m [0m[7m [0m[7m [0m[49m [0m[7m [0m[7m [0m[49m [0m[7m [0m[7m [0m[7m [0m[7m [0m
|
|
38
|
+
[7m [0m[7m [0m[7m [0m[49m [0m[49m [0m[7m [0m[49m [0m[49m [0m[49m [0m[49m [0m[49m [0m[49m [0m[7m [0m[7m [0m[7m [0m[49m [0m[7m [0m[49m [0m[7m [0m[7m [0m[49m [0m[49m [0m[7m [0m[49m [0m[7m [0m[7m [0m[7m [0m
|
|
39
|
+
[7m [0m[7m [0m[7m [0m[49m [0m[7m [0m[49m [0m[49m [0m[7m [0m[7m [0m[49m [0m[49m [0m[7m [0m[7m [0m[49m [0m[49m [0m[7m [0m[49m [0m[7m [0m[49m [0m[7m [0m[49m [0m[7m [0m[7m [0m[49m [0m[49m [0m[49m [0m[7m [0m
|
|
40
|
+
[7m [0m[49m [0m[7m [0m[7m [0m[7m [0m[7m [0m[49m [0m[49m [0m[7m [0m[49m [0m[7m [0m[7m [0m[49m [0m[7m [0m[7m [0m[7m [0m[49m [0m[49m [0m[49m [0m[7m [0m[49m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m
|
|
41
|
+
[7m [0m[49m [0m[7m [0m[49m [0m[49m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[49m [0m[49m [0m[7m [0m[49m [0m[49m [0m[49m [0m[49m [0m[49m [0m[7m [0m[7m [0m[49m [0m[49m [0m[49m [0m[7m [0m[7m [0m[7m [0m[7m [0m
|
|
42
|
+
[7m [0m[7m [0m[7m [0m[7m [0m[49m [0m[7m [0m[49m [0m[49m [0m[49m [0m[7m [0m[49m [0m[7m [0m[49m [0m[49m [0m[7m [0m[49m [0m[49m [0m[49m [0m[7m [0m[7m [0m[7m [0m[7m [0m[49m [0m[7m [0m[49m [0m[49m [0m[7m [0m
|
|
43
|
+
[7m [0m[7m [0m[7m [0m[49m [0m[49m [0m[7m [0m[49m [0m[7m [0m[7m [0m[49m [0m[7m [0m[7m [0m[49m [0m[7m [0m[49m [0m[7m [0m[49m [0m[7m [0m[7m [0m[49m [0m[7m [0m[7m [0m[49m [0m[49m [0m[7m [0m[7m [0m[7m [0m
|
|
44
|
+
[7m [0m[49m [0m[49m [0m[7m [0m[49m [0m[49m [0m[7m [0m[49m [0m[7m [0m[49m [0m[7m [0m[49m [0m[7m [0m[49m [0m[7m [0m[7m [0m[49m [0m[49m [0m[49m [0m[49m [0m[49m [0m[49m [0m[49m [0m[49m [0m[7m [0m[49m [0m[7m [0m
|
|
45
|
+
[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[49m [0m[7m [0m[7m [0m[7m [0m[7m [0m[49m [0m[49m [0m[7m [0m[49m [0m[7m [0m[7m [0m[7m [0m[49m [0m[49m [0m[49m [0m[49m [0m[7m [0m[7m [0m
|
|
46
|
+
[7m [0m[49m [0m[49m [0m[49m [0m[49m [0m[49m [0m[49m [0m[49m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[49m [0m[49m [0m[7m [0m[49m [0m[7m [0m[49m [0m[7m [0m[49m [0m[49m [0m[49m [0m[7m [0m
|
|
47
|
+
[7m [0m[49m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[49m [0m[7m [0m[49m [0m[7m [0m[7m [0m[7m [0m[49m [0m[7m [0m[7m [0m[7m [0m[49m [0m[7m [0m[7m [0m[7m [0m[49m [0m[7m [0m[7m [0m[7m [0m[49m [0m[7m [0m
|
|
48
|
+
[7m [0m[49m [0m[7m [0m[49m [0m[49m [0m[49m [0m[7m [0m[49m [0m[7m [0m[49m [0m[49m [0m[49m [0m[49m [0m[7m [0m[49m [0m[49m [0m[7m [0m[49m [0m[49m [0m[49m [0m[49m [0m[49m [0m[49m [0m[7m [0m[49m [0m[7m [0m[7m [0m
|
|
49
|
+
[7m [0m[49m [0m[7m [0m[49m [0m[49m [0m[49m [0m[7m [0m[49m [0m[7m [0m[7m [0m[49m [0m[7m [0m[7m [0m[49m [0m[7m [0m[49m [0m[7m [0m[7m [0m[7m [0m[49m [0m[7m [0m[49m [0m[7m [0m[7m [0m[7m [0m[49m [0m[7m [0m
|
|
50
|
+
[7m [0m[49m [0m[7m [0m[49m [0m[49m [0m[49m [0m[7m [0m[49m [0m[7m [0m[7m [0m[49m [0m[49m [0m[49m [0m[49m [0m[49m [0m[49m [0m[7m [0m[49m [0m[49m [0m[7m [0m[49m [0m[7m [0m[49m [0m[49m [0m[49m [0m[7m [0m[7m [0m
|
|
51
|
+
[7m [0m[49m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[49m [0m[7m [0m[49m [0m[49m [0m[7m [0m[49m [0m[7m [0m[49m [0m[7m [0m[49m [0m[7m [0m[7m [0m[7m [0m[49m [0m[7m [0m[49m [0m[49m [0m[49m [0m[49m [0m[7m [0m
|
|
52
|
+
[7m [0m[49m [0m[49m [0m[49m [0m[49m [0m[49m [0m[49m [0m[49m [0m[7m [0m[49m [0m[7m [0m[7m [0m[7m [0m[49m [0m[49m [0m[7m [0m[49m [0m[7m [0m[7m [0m[7m [0m[49m [0m[49m [0m[49m [0m[49m [0m[7m [0m[49m [0m[7m [0m
|
|
53
|
+
[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m
|
|
54
|
+
|
|
55
|
+
c3f3097e2d129a.lhr.life tunneled with tls termination, https://c3f3097e2d129a.lhr.life
|
|
56
|
+
create an account and add your key for a longer lasting domain name. see https://localhost.run/docs/forever-free/ for more information.
|
|
57
|
+
Open your tunnel address on your mobile with this QR:
|
|
58
|
+
|
|
59
|
+
[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m
|
|
60
|
+
[7m [0m[49m [0m[49m [0m[49m [0m[49m [0m[49m [0m[49m [0m[49m [0m[7m [0m[7m [0m[7m [0m[49m [0m[7m [0m[7m [0m[49m [0m[7m [0m[7m [0m[49m [0m[7m [0m[49m [0m[49m [0m[49m [0m[49m [0m[49m [0m[49m [0m[49m [0m[7m [0m
|
|
61
|
+
[7m [0m[49m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[49m [0m[7m [0m[7m [0m[49m [0m[7m [0m[7m [0m[7m [0m[49m [0m[7m [0m[49m [0m[49m [0m[7m [0m[49m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[49m [0m[7m [0m
|
|
62
|
+
[7m [0m[49m [0m[7m [0m[49m [0m[49m [0m[49m [0m[7m [0m[49m [0m[7m [0m[49m [0m[7m [0m[7m [0m[49m [0m[7m [0m[49m [0m[49m [0m[49m [0m[49m [0m[7m [0m[49m [0m[7m [0m[49m [0m[49m [0m[49m [0m[7m [0m[49m [0m[7m [0m
|
|
63
|
+
[7m [0m[49m [0m[7m [0m[49m [0m[49m [0m[49m [0m[7m [0m[49m [0m[7m [0m[7m [0m[49m [0m[7m [0m[7m [0m[49m [0m[7m [0m[49m [0m[7m [0m[49m [0m[7m [0m[49m [0m[7m [0m[49m [0m[49m [0m[49m [0m[7m [0m[49m [0m[7m [0m
|
|
64
|
+
[7m [0m[49m [0m[7m [0m[49m [0m[49m [0m[49m [0m[7m [0m[49m [0m[7m [0m[7m [0m[49m [0m[7m [0m[7m [0m[49m [0m[49m [0m[49m [0m[49m [0m[49m [0m[7m [0m[49m [0m[7m [0m[49m [0m[49m [0m[49m [0m[7m [0m[49m [0m[7m [0m
|
|
65
|
+
[7m [0m[49m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[49m [0m[7m [0m[7m [0m[7m [0m[49m [0m[49m [0m[49m [0m[7m [0m[49m [0m[7m [0m[49m [0m[7m [0m[49m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[49m [0m[7m [0m
|
|
66
|
+
[7m [0m[49m [0m[49m [0m[49m [0m[49m [0m[49m [0m[49m [0m[49m [0m[7m [0m[49m [0m[7m [0m[49m [0m[7m [0m[49m [0m[7m [0m[49m [0m[7m [0m[49m [0m[7m [0m[49m [0m[49m [0m[49m [0m[49m [0m[49m [0m[49m [0m[49m [0m[7m [0m
|
|
67
|
+
[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[49m [0m[7m [0m[7m [0m[49m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m
|
|
68
|
+
[7m [0m[49m [0m[49m [0m[49m [0m[7m [0m[49m [0m[49m [0m[49m [0m[49m [0m[49m [0m[7m [0m[7m [0m[7m [0m[49m [0m[49m [0m[7m [0m[49m [0m[7m [0m[49m [0m[49m [0m[7m [0m[7m [0m[7m [0m[49m [0m[7m [0m[7m [0m[7m [0m
|
|
69
|
+
[7m [0m[49m [0m[7m [0m[7m [0m[49m [0m[49m [0m[7m [0m[7m [0m[7m [0m[7m [0m[49m [0m[7m [0m[49m [0m[49m [0m[7m [0m[7m [0m[7m [0m[49m [0m[49m [0m[49m [0m[49m [0m[7m [0m[7m [0m[7m [0m[49m [0m[49m [0m[7m [0m
|
|
70
|
+
[7m [0m[49m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[49m [0m[7m [0m[49m [0m[7m [0m[49m [0m[49m [0m[49m [0m[7m [0m[7m [0m[49m [0m[7m [0m[49m [0m[49m [0m[7m [0m[49m [0m[49m [0m[49m [0m[49m [0m[49m [0m[7m [0m
|
|
71
|
+
[7m [0m[7m [0m[49m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[49m [0m[49m [0m[49m [0m[7m [0m[49m [0m[7m [0m[7m [0m[7m [0m[49m [0m[49m [0m[7m [0m[49m [0m[7m [0m[7m [0m[7m [0m[49m [0m[49m [0m[7m [0m
|
|
72
|
+
[7m [0m[49m [0m[49m [0m[7m [0m[49m [0m[49m [0m[7m [0m[49m [0m[49m [0m[7m [0m[7m [0m[49m [0m[49m [0m[7m [0m[49m [0m[7m [0m[7m [0m[49m [0m[7m [0m[7m [0m[7m [0m[49m [0m[7m [0m[49m [0m[7m [0m[7m [0m[7m [0m
|
|
73
|
+
[7m [0m[7m [0m[7m [0m[7m [0m[49m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[49m [0m[49m [0m[49m [0m[7m [0m[49m [0m[7m [0m[7m [0m[49m [0m[49m [0m[49m [0m[7m [0m[49m [0m[7m [0m[7m [0m[49m [0m[49m [0m[7m [0m
|
|
74
|
+
[7m [0m[49m [0m[7m [0m[49m [0m[49m [0m[7m [0m[49m [0m[49m [0m[49m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[49m [0m[49m [0m[49m [0m[7m [0m[49m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m
|
|
75
|
+
[7m [0m[7m [0m[49m [0m[7m [0m[49m [0m[49m [0m[49m [0m[7m [0m[49m [0m[49m [0m[7m [0m[49m [0m[49m [0m[7m [0m[7m [0m[7m [0m[7m [0m[49m [0m[49m [0m[7m [0m[7m [0m[7m [0m[49m [0m[7m [0m[7m [0m[7m [0m[7m [0m
|
|
76
|
+
[7m [0m[49m [0m[7m [0m[49m [0m[7m [0m[49m [0m[49m [0m[49m [0m[7m [0m[49m [0m[7m [0m[49m [0m[7m [0m[49m [0m[49m [0m[7m [0m[49m [0m[49m [0m[49m [0m[49m [0m[49m [0m[49m [0m[49m [0m[7m [0m[7m [0m[49m [0m[7m [0m
|
|
77
|
+
[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[49m [0m[49m [0m[7m [0m[49m [0m[49m [0m[49m [0m[7m [0m[7m [0m[49m [0m[7m [0m[7m [0m[7m [0m[49m [0m[7m [0m[49m [0m[7m [0m[49m [0m[7m [0m
|
|
78
|
+
[7m [0m[49m [0m[49m [0m[49m [0m[49m [0m[49m [0m[49m [0m[49m [0m[7m [0m[49m [0m[7m [0m[49m [0m[49m [0m[49m [0m[7m [0m[49m [0m[7m [0m[49m [0m[7m [0m[49m [0m[7m [0m[49m [0m[49m [0m[49m [0m[7m [0m[7m [0m[7m [0m
|
|
79
|
+
[7m [0m[49m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[49m [0m[7m [0m[49m [0m[7m [0m[7m [0m[7m [0m[49m [0m[49m [0m[7m [0m[7m [0m[49m [0m[7m [0m[7m [0m[7m [0m[49m [0m[7m [0m[49m [0m[7m [0m[49m [0m[7m [0m
|
|
80
|
+
[7m [0m[49m [0m[7m [0m[49m [0m[49m [0m[49m [0m[7m [0m[49m [0m[7m [0m[49m [0m[49m [0m[49m [0m[49m [0m[7m [0m[7m [0m[49m [0m[7m [0m[49m [0m[49m [0m[49m [0m[49m [0m[49m [0m[49m [0m[49m [0m[49m [0m[7m [0m[7m [0m
|
|
81
|
+
[7m [0m[49m [0m[7m [0m[49m [0m[49m [0m[49m [0m[7m [0m[49m [0m[7m [0m[7m [0m[7m [0m[49m [0m[49m [0m[7m [0m[7m [0m[7m [0m[49m [0m[7m [0m[49m [0m[49m [0m[7m [0m[7m [0m[49m [0m[7m [0m[49m [0m[7m [0m[7m [0m
|
|
82
|
+
[7m [0m[49m [0m[7m [0m[49m [0m[49m [0m[49m [0m[7m [0m[49m [0m[7m [0m[49m [0m[7m [0m[49m [0m[7m [0m[7m [0m[49m [0m[7m [0m[7m [0m[49m [0m[7m [0m[49m [0m[49m [0m[49m [0m[7m [0m[7m [0m[7m [0m[49m [0m[7m [0m
|
|
83
|
+
[7m [0m[49m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[49m [0m[7m [0m[49m [0m[49m [0m[49m [0m[49m [0m[7m [0m[7m [0m[7m [0m[7m [0m[49m [0m[7m [0m[7m [0m[7m [0m[49m [0m[49m [0m[7m [0m[49m [0m[49m [0m[7m [0m
|
|
84
|
+
[7m [0m[49m [0m[49m [0m[49m [0m[49m [0m[49m [0m[49m [0m[49m [0m[7m [0m[49m [0m[49m [0m[49m [0m[7m [0m[49m [0m[7m [0m[7m [0m[49m [0m[49m [0m[7m [0m[7m [0m[49m [0m[7m [0m[49m [0m[49m [0m[7m [0m[49m [0m[7m [0m
|
|
85
|
+
[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m[7m [0m
|
|
86
|
+
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -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)
|
|
Binary file
|
|
Binary file
|
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 (
|
|
669
|
+
h1("Step 6 of 7 — Remote Access (localtunnel / 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 —
|
|
678
|
-
|
|
677
|
+
{BOLD}Option A — localtunnel (easiest + free){RESET}
|
|
678
|
+
localtunnel creates a public HTTPS URL that tunnels to your local port 5000.
|
|
679
679
|
No account required.
|
|
680
|
-
Official site: {CYAN}https://
|
|
680
|
+
Official site: {CYAN}https://theboroer.github.io/localtunnel-www/{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
|
|
696
|
+
choice = ask_yn("Enable localtunnel on startup?", default=True)
|
|
697
697
|
if not choice:
|
|
698
698
|
LOCALTUNNEL_ENABLED.unlink(missing_ok=True)
|
|
699
|
-
warn("Skipping
|
|
699
|
+
warn("Skipping localtunnel. 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
|
|
714
|
+
warn("Skipping localtunnel 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("
|
|
720
|
+
ok("localtunnel auto-start enabled")
|
|
721
721
|
nl()
|
|
722
|
-
info("
|
|
722
|
+
info("localtunnel will be started automatically every time you run start.py.")
|
|
723
723
|
|
|
724
724
|
# ══════════════════════════════════════════════════════════════════════════════
|
|
725
725
|
# Step 6 — Access Token & Database
|
|
@@ -754,95 +754,83 @@ def setup_token_and_db():
|
|
|
754
754
|
# ══════════════════════════════════════════════════════════════════════════════
|
|
755
755
|
# Launch Backend
|
|
756
756
|
# ══════════════════════════════════════════════════════════════════════════════
|
|
757
|
-
def
|
|
758
|
-
"""Extract first
|
|
757
|
+
def _extract_localtunnel_url(text: str) -> str | None:
|
|
758
|
+
"""Extract first localtunnel public URL from text."""
|
|
759
759
|
import re
|
|
760
|
-
|
|
761
|
-
|
|
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
|
|
760
|
+
m = re.search(r"https://[\w.-]+\.loca\.lt\b", text)
|
|
761
|
+
return m.group(0) if m else None
|
|
767
762
|
|
|
768
763
|
|
|
769
|
-
def
|
|
764
|
+
def _find_localtunnel_url_from_log() -> str | None:
|
|
770
765
|
"""Read local tunnel log and return detected public URL if available."""
|
|
771
766
|
try:
|
|
772
767
|
if not LOCALTUNNEL_LOG.exists():
|
|
773
768
|
return None
|
|
774
769
|
text = LOCALTUNNEL_LOG.read_text(encoding="utf-8", errors="ignore")
|
|
775
|
-
return
|
|
770
|
+
return _extract_localtunnel_url(text)
|
|
776
771
|
except Exception:
|
|
777
772
|
return None
|
|
778
773
|
|
|
779
774
|
|
|
780
|
-
def
|
|
781
|
-
"""Stop existing
|
|
775
|
+
def _stop_localtunnel_processes():
|
|
776
|
+
"""Stop existing localtunnel processes so only one tunnel remains active."""
|
|
782
777
|
try:
|
|
783
778
|
if IS_WINDOWS:
|
|
784
779
|
script = (
|
|
785
780
|
"Get-CimInstance Win32_Process "
|
|
786
|
-
"| Where-Object { $_.CommandLine -match '
|
|
781
|
+
"| Where-Object { $_.CommandLine -match 'localtunnel|\\.loca\\.lt' } "
|
|
787
782
|
"| ForEach-Object { Stop-Process -Id $_.ProcessId -Force -ErrorAction SilentlyContinue }"
|
|
788
783
|
)
|
|
789
784
|
subprocess.run(["powershell", "-NoProfile", "-Command", script], check=False)
|
|
790
785
|
else:
|
|
791
|
-
subprocess.run(["pkill", "-f", "
|
|
786
|
+
subprocess.run(["pkill", "-f", "localtunnel"], check=False)
|
|
792
787
|
except Exception:
|
|
793
788
|
pass
|
|
794
789
|
|
|
795
790
|
|
|
796
|
-
def
|
|
797
|
-
"""Start
|
|
791
|
+
def _start_localtunnel(port: int, timeout: int = 25) -> str | None:
|
|
792
|
+
"""Start localtunnel in the background and wait for the public URL."""
|
|
798
793
|
import time as _time
|
|
799
794
|
|
|
800
|
-
|
|
801
|
-
if not
|
|
802
|
-
warn("SSH is required to use localhost.run. Please install OpenSSH.")
|
|
795
|
+
npx_exec = shutil.which("npx") or shutil.which("npx.cmd")
|
|
796
|
+
if not npx_exec:
|
|
803
797
|
return None
|
|
804
798
|
|
|
805
|
-
# Clean stale
|
|
806
|
-
|
|
799
|
+
# Clean stale localtunnel processes.
|
|
800
|
+
_stop_localtunnel_processes()
|
|
807
801
|
_time.sleep(0.8)
|
|
808
802
|
|
|
809
|
-
info("Starting
|
|
803
|
+
info("Starting localtunnel in background …")
|
|
810
804
|
try:
|
|
811
805
|
LOCALTUNNEL_LOG.parent.mkdir(parents=True, exist_ok=True)
|
|
812
806
|
LOCALTUNNEL_LOG.write_text("")
|
|
813
807
|
|
|
814
808
|
log_handle = open(LOCALTUNNEL_LOG, "a", encoding="utf-8", buffering=1)
|
|
815
809
|
kwargs = {
|
|
810
|
+
"start_new_session": True,
|
|
816
811
|
"stdout": log_handle,
|
|
817
812
|
"stderr": subprocess.STDOUT,
|
|
818
|
-
"stdin": subprocess.DEVNULL,
|
|
819
813
|
"text": True,
|
|
820
814
|
}
|
|
821
|
-
if IS_WINDOWS:
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
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
|
-
|
|
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)
|
|
832
820
|
except Exception as e:
|
|
833
|
-
warn(f"Could not start
|
|
821
|
+
warn(f"Could not start localtunnel: {e}")
|
|
834
822
|
return None
|
|
835
823
|
|
|
836
824
|
# Poll log output until URL is emitted.
|
|
837
825
|
deadline = _time.time() + timeout
|
|
838
826
|
while _time.time() < deadline:
|
|
839
827
|
_time.sleep(1)
|
|
840
|
-
url =
|
|
828
|
+
url = _find_localtunnel_url_from_log()
|
|
841
829
|
if url:
|
|
842
|
-
ok(f"
|
|
830
|
+
ok(f"localtunnel active → {GREEN}{BOLD}{url}{RESET}")
|
|
843
831
|
return url
|
|
844
832
|
|
|
845
|
-
warn("
|
|
833
|
+
warn("localtunnel started but URL is not available yet.")
|
|
846
834
|
info(f"Check tunnel logs in: {LOCALTUNNEL_LOG}")
|
|
847
835
|
return None
|
|
848
836
|
|
|
@@ -1214,26 +1202,26 @@ def launch_backend():
|
|
|
1214
1202
|
token = TOKEN_FILE.read_text().strip() if TOKEN_FILE.exists() else "—"
|
|
1215
1203
|
local_ip = _detect_local_ip()
|
|
1216
1204
|
|
|
1217
|
-
|
|
1205
|
+
localtunnel_enabled = bool(shutil.which("npx") or shutil.which("npx.cmd"))
|
|
1218
1206
|
|
|
1219
|
-
|
|
1220
|
-
if
|
|
1221
|
-
|
|
1207
|
+
localtunnel_url: str | None = None
|
|
1208
|
+
if localtunnel_enabled:
|
|
1209
|
+
localtunnel_url = _start_localtunnel(PORT)
|
|
1222
1210
|
else:
|
|
1223
|
-
|
|
1211
|
+
localtunnel_url = _find_localtunnel_url_from_log()
|
|
1224
1212
|
|
|
1225
|
-
if
|
|
1226
|
-
tunnel_line = f" Public URL → {GREEN}{BOLD}{
|
|
1227
|
-
tunnel_hint = f" · public → {GREEN}{
|
|
1228
|
-
elif
|
|
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:
|
|
1229
1217
|
tunnel_line = f" Public URL → {YELLOW}(starting — URL pending, check localtunnel.log){RESET}"
|
|
1230
|
-
tunnel_hint = f" · public → run: {DIM}
|
|
1218
|
+
tunnel_hint = f" · public → run: {DIM}npx localtunnel --port {PORT}{RESET}"
|
|
1231
1219
|
else:
|
|
1232
1220
|
tunnel_line = ""
|
|
1233
|
-
tunnel_hint = f" · public → install
|
|
1221
|
+
tunnel_hint = f" · public → install Node.js first, then run: {DIM}npx localtunnel --port {PORT}{RESET}"
|
|
1234
1222
|
|
|
1235
1223
|
# ── Generate and display QR code ──────────────────────────────────────────
|
|
1236
|
-
qr_url =
|
|
1224
|
+
qr_url = localtunnel_url if localtunnel_url else f"http://{local_ip}:{PORT}"
|
|
1237
1225
|
_display_connect_qr(qr_url, token)
|
|
1238
1226
|
|
|
1239
1227
|
print(f"""
|
|
@@ -1294,7 +1282,7 @@ def launch_backend_status():
|
|
|
1294
1282
|
url = match.group(1)
|
|
1295
1283
|
|
|
1296
1284
|
if url == "NOT_FOUND":
|
|
1297
|
-
warn("Could not find a running
|
|
1285
|
+
warn("Could not find a running localtunnel URL in config/localtunnel.log.")
|
|
1298
1286
|
nl()
|
|
1299
1287
|
print(" Wait 5 seconds, or run 'superbrain-server' to start the server.")
|
|
1300
1288
|
return
|
|
@@ -1306,11 +1294,11 @@ def launch_backend_status():
|
|
|
1306
1294
|
network_url = f"http://{local_ip}:5000"
|
|
1307
1295
|
|
|
1308
1296
|
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
|
|
1297
|
+
print(f" Local URL \u2192 {CYAN}{local_url}{RESET}")
|
|
1298
|
+
print(f" Network URL \u2192 {CYAN}{network_url}{RESET}")
|
|
1299
|
+
print(f" Public URL \u2192 {CYAN}{url}{RESET} (localtunnel)")
|
|
1300
|
+
print(f" API docs \u2192 {CYAN}{local_url}/docs{RESET}")
|
|
1301
|
+
print(f" Access Token \u2192 {BOLD}{MAGENTA}{token}{RESET}")
|
|
1314
1302
|
nl()
|
|
1315
1303
|
|
|
1316
1304
|
def main():
|
|
@@ -1339,7 +1327,7 @@ def main():
|
|
|
1339
1327
|
3 · Configure AI provider keys + Instagram credentials
|
|
1340
1328
|
4 · Set up an offline AI model via Ollama (qwen3-vl:4b)
|
|
1341
1329
|
5 · Set up offline audio transcription (Whisper + ffmpeg)
|
|
1342
|
-
6 · Configure remote access (
|
|
1330
|
+
6 · Configure remote access (localtunnel or port forwarding)
|
|
1343
1331
|
7 · Generate Access Token & initialise database
|
|
1344
1332
|
|
|
1345
1333
|
Press {BOLD}Enter{RESET} to accept defaults shown in [{DIM}brackets{RESET}].
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
"""
|
|
2
|
+
SuperBrain - Comprehensive Backend Test Suite
|
|
3
|
+
Tests all API endpoints against the running backend.
|
|
4
|
+
"""
|
|
5
|
+
import requests
|
|
6
|
+
import sys
|
|
7
|
+
import json
|
|
8
|
+
import time
|
|
9
|
+
|
|
10
|
+
# Config
|
|
11
|
+
BASE_URL = "http://localhost:5000"
|
|
12
|
+
TOKEN = "4XVTLWWV"
|
|
13
|
+
HEADERS = {"X-API-Key": TOKEN, "Content-Type": "application/json"}
|
|
14
|
+
|
|
15
|
+
TEST_YT_URL = "https://www.youtube.com/watch?v=dQw4w9WgXcQ"
|
|
16
|
+
|
|
17
|
+
passed = 0
|
|
18
|
+
failed = 0
|
|
19
|
+
skipped = 0
|
|
20
|
+
|
|
21
|
+
def test(name, condition, detail=""):
|
|
22
|
+
global passed, failed
|
|
23
|
+
if condition:
|
|
24
|
+
passed += 1
|
|
25
|
+
print(f" [PASS] {name}")
|
|
26
|
+
else:
|
|
27
|
+
failed += 1
|
|
28
|
+
print(f" [FAIL] {name} -- {detail}")
|
|
29
|
+
|
|
30
|
+
def skip(name, reason):
|
|
31
|
+
global skipped
|
|
32
|
+
skipped += 1
|
|
33
|
+
print(f" [SKIP] {name} -- {reason}")
|
|
34
|
+
|
|
35
|
+
# Phase 1: Health & Auth
|
|
36
|
+
print("\n--- Phase 1: Health & Auth ---")
|
|
37
|
+
|
|
38
|
+
try:
|
|
39
|
+
r = requests.get(f"{BASE_URL}/status", timeout=5)
|
|
40
|
+
test("1.1 GET /status", r.status_code == 200 and r.json().get("status") == "online", f"status={r.status_code}")
|
|
41
|
+
except Exception as e:
|
|
42
|
+
test("1.1 GET /status", False, str(e))
|
|
43
|
+
|
|
44
|
+
try:
|
|
45
|
+
r = requests.get(f"{BASE_URL}/health", headers=HEADERS, timeout=5)
|
|
46
|
+
data = r.json()
|
|
47
|
+
test("1.2 GET /health (auth)", r.status_code == 200 and "database" in data, f"status={r.status_code}")
|
|
48
|
+
except Exception as e:
|
|
49
|
+
test("1.2 GET /health", False, str(e))
|
|
50
|
+
|
|
51
|
+
try:
|
|
52
|
+
r = requests.get(f"{BASE_URL}/health", headers={"X-API-Key": "WRONGTKN"}, timeout=5)
|
|
53
|
+
test("1.3 Auth rejection (wrong)", r.status_code == 401, f"status={r.status_code}")
|
|
54
|
+
except Exception as e:
|
|
55
|
+
test("1.3 Auth rejection", False, str(e))
|
|
56
|
+
|
|
57
|
+
try:
|
|
58
|
+
r = requests.get(f"{BASE_URL}/health", timeout=5)
|
|
59
|
+
test("1.4 Auth rejection (none)", r.status_code in [401, 403, 422], f"status={r.status_code}")
|
|
60
|
+
except Exception as e:
|
|
61
|
+
test("1.4 Auth rejection (none)", False, str(e))
|
|
62
|
+
|
|
63
|
+
# Phase 2: Read Endpoints
|
|
64
|
+
print("\n--- Phase 2: Read Endpoints ---")
|
|
65
|
+
|
|
66
|
+
try:
|
|
67
|
+
r = requests.get(f"{BASE_URL}/recent?limit=10", headers=HEADERS, timeout=10)
|
|
68
|
+
data = r.json()
|
|
69
|
+
test("2.1 GET /recent", r.status_code == 200 and "data" in data, f"status={r.status_code}")
|
|
70
|
+
print(f" found {len(data.get('data', []))} posts")
|
|
71
|
+
except Exception as e:
|
|
72
|
+
test("2.1 GET /recent", False, str(e))
|
|
73
|
+
|
|
74
|
+
try:
|
|
75
|
+
r = requests.get(f"{BASE_URL}/categories", headers=HEADERS, timeout=10)
|
|
76
|
+
data = r.json()
|
|
77
|
+
test("2.2 GET /categories", r.status_code == 200 and "categories" in data, f"status={r.status_code}")
|
|
78
|
+
print(f" found {len(data.get('categories', []))} categories")
|
|
79
|
+
except Exception as e:
|
|
80
|
+
test("2.2 GET /categories", False, str(e))
|
|
81
|
+
|
|
82
|
+
try:
|
|
83
|
+
r = requests.get(f"{BASE_URL}/queue-status", headers=HEADERS, timeout=10)
|
|
84
|
+
data = r.json()
|
|
85
|
+
test("2.3 GET /queue-status", r.status_code == 200 and "processing_count" in data, f"keys={list(data.keys())}")
|
|
86
|
+
print(f" processing={data.get('processing_count',0)}, queue={data.get('queue_count',0)}, retry={data.get('retry_count',0)}")
|
|
87
|
+
except Exception as e:
|
|
88
|
+
test("2.3 GET /queue-status", False, str(e))
|
|
89
|
+
|
|
90
|
+
try:
|
|
91
|
+
r = requests.get(f"{BASE_URL}/stats", headers=HEADERS, timeout=10)
|
|
92
|
+
test("2.4 GET /stats", r.status_code == 200, f"status={r.status_code}")
|
|
93
|
+
except Exception as e:
|
|
94
|
+
test("2.4 GET /stats", False, str(e))
|
|
95
|
+
|
|
96
|
+
try:
|
|
97
|
+
r = requests.get(f"{BASE_URL}/search?tags=travel&limit=5", headers=HEADERS, timeout=10)
|
|
98
|
+
test("2.5 GET /search?tags=travel", r.status_code == 200, f"status={r.status_code}")
|
|
99
|
+
except Exception as e:
|
|
100
|
+
test("2.5 GET /search", False, str(e))
|
|
101
|
+
|
|
102
|
+
try:
|
|
103
|
+
r = requests.get(f"{BASE_URL}/queue/retry", headers=HEADERS, timeout=10)
|
|
104
|
+
test("2.6 GET /queue/retry", r.status_code == 200, f"status={r.status_code}")
|
|
105
|
+
except Exception as e:
|
|
106
|
+
test("2.6 GET /queue/retry", False, str(e))
|
|
107
|
+
|
|
108
|
+
# Phase 3: Collections CRUD
|
|
109
|
+
print("\n--- Phase 3: Collections CRUD ---")
|
|
110
|
+
|
|
111
|
+
test_collection_id = None
|
|
112
|
+
|
|
113
|
+
try:
|
|
114
|
+
r = requests.get(f"{BASE_URL}/collections", headers=HEADERS, timeout=10)
|
|
115
|
+
data = r.json()
|
|
116
|
+
test("3.1 GET /collections", r.status_code == 200, f"status={r.status_code}")
|
|
117
|
+
print(f" found {len(data)} collections")
|
|
118
|
+
except Exception as e:
|
|
119
|
+
test("3.1 GET /collections", False, str(e))
|
|
120
|
+
|
|
121
|
+
try:
|
|
122
|
+
r = requests.post(f"{BASE_URL}/collections", headers=HEADERS, json={"id": "test-id", "name": "Test Collection", "icon": "test"}, timeout=10)
|
|
123
|
+
data = r.json()
|
|
124
|
+
test("3.2 POST /collections (create)", r.status_code == 200 and data.get("id"), f"status={r.status_code}")
|
|
125
|
+
test_collection_id = data.get("id")
|
|
126
|
+
print(f" created ID: {test_collection_id}")
|
|
127
|
+
except Exception as e:
|
|
128
|
+
test("3.2 POST /collections", False, str(e))
|
|
129
|
+
|
|
130
|
+
if test_collection_id:
|
|
131
|
+
try:
|
|
132
|
+
r = requests.post(f"{BASE_URL}/collections", headers=HEADERS, json={"id": test_collection_id, "name": "Updated Test", "icon": "ok"}, timeout=10)
|
|
133
|
+
test("3.3 POST /collections (update)", r.status_code == 200, f"status={r.status_code}")
|
|
134
|
+
except Exception as e:
|
|
135
|
+
test("3.3 POST /collections (update)", False, str(e))
|
|
136
|
+
|
|
137
|
+
try:
|
|
138
|
+
r = requests.delete(f"{BASE_URL}/collections/{test_collection_id}", headers=HEADERS, timeout=10)
|
|
139
|
+
test("3.4 DELETE /collections", r.status_code == 200, f"status={r.status_code}")
|
|
140
|
+
except Exception as e:
|
|
141
|
+
test("3.4 DELETE /collections", False, str(e))
|
|
142
|
+
else:
|
|
143
|
+
skip("3.3 PUT /collections", "no collection created")
|
|
144
|
+
skip("3.4 DELETE /collections", "no collection created")
|
|
145
|
+
|
|
146
|
+
try:
|
|
147
|
+
r = requests.get(f"{BASE_URL}/collections", headers=HEADERS, timeout=10)
|
|
148
|
+
data = r.json()
|
|
149
|
+
collections = data.get("data", [])
|
|
150
|
+
wl = next((c for c in collections if c.get("name") == "Watch Later"), None)
|
|
151
|
+
if wl:
|
|
152
|
+
r = requests.delete(f"{BASE_URL}/collections/{wl['id']}", headers=HEADERS, timeout=10)
|
|
153
|
+
test("3.5 Watch Later protection", r.status_code in [400, 403], f"status={r.status_code} (should be blocked)")
|
|
154
|
+
else:
|
|
155
|
+
skip("3.5 Watch Later protection", "no Watch Later found")
|
|
156
|
+
except Exception as e:
|
|
157
|
+
test("3.5 Watch Later protection", False, str(e))
|
|
158
|
+
|
|
159
|
+
# Phase 4: Settings
|
|
160
|
+
print("\n--- Phase 4: Settings ---")
|
|
161
|
+
|
|
162
|
+
try:
|
|
163
|
+
r = requests.get(f"{BASE_URL}/settings/ai-providers", headers=HEADERS, timeout=10)
|
|
164
|
+
test("4.1 GET /settings/ai-providers", r.status_code == 200, f"status={r.status_code}")
|
|
165
|
+
except Exception as e:
|
|
166
|
+
test("4.1 GET /settings/ai-providers", False, str(e))
|
|
167
|
+
|
|
168
|
+
try:
|
|
169
|
+
r = requests.post(f"{BASE_URL}/settings/ai-providers", headers=HEADERS,
|
|
170
|
+
json={"provider": "groq", "api_key": "gsk_test"}, timeout=10)
|
|
171
|
+
test("4.2 POST ai-providers (Groq)", r.status_code == 200, f"status={r.status_code}")
|
|
172
|
+
except Exception as e:
|
|
173
|
+
test("4.2 POST ai-providers", False, str(e))
|
|
174
|
+
|
|
175
|
+
try:
|
|
176
|
+
r = requests.get(f"{BASE_URL}/settings/instagram", headers=HEADERS, timeout=10)
|
|
177
|
+
test("4.3 GET /settings/instagram", r.status_code == 200, f"status={r.status_code}")
|
|
178
|
+
except Exception as e:
|
|
179
|
+
test("4.3 GET /settings/instagram", False, str(e))
|
|
180
|
+
|
|
181
|
+
try:
|
|
182
|
+
r = requests.post(f"{BASE_URL}/settings/instagram", headers=HEADERS,
|
|
183
|
+
json={"username": "testuser", "password": "testpass"}, timeout=10)
|
|
184
|
+
test("4.4 POST /settings/instagram", r.status_code == 200, f"status={r.status_code}")
|
|
185
|
+
except Exception as e:
|
|
186
|
+
test("4.4 POST /settings/instagram", False, str(e))
|
|
187
|
+
|
|
188
|
+
# Phase 5: Export/Import
|
|
189
|
+
print("\n--- Phase 5: Export/Import ---")
|
|
190
|
+
|
|
191
|
+
try:
|
|
192
|
+
r = requests.get(f"{BASE_URL}/export", headers=HEADERS, timeout=15)
|
|
193
|
+
test("5.1 GET /export", r.status_code == 200, f"status={r.status_code}")
|
|
194
|
+
ed = r.json()
|
|
195
|
+
print(f" exported {len(ed.get('posts', []))} posts, {len(ed.get('collections', []))} collections")
|
|
196
|
+
except Exception as e:
|
|
197
|
+
test("5.1 GET /export", False, str(e))
|
|
198
|
+
|
|
199
|
+
try:
|
|
200
|
+
r = requests.post(f"{BASE_URL}/import", headers=HEADERS,
|
|
201
|
+
json={"posts": [], "collections": [], "mode": "merge"}, timeout=15)
|
|
202
|
+
test("5.2 POST /import (merge, empty)", r.status_code == 200, f"status={r.status_code}")
|
|
203
|
+
except Exception as e:
|
|
204
|
+
test("5.2 POST /import", False, str(e))
|
|
205
|
+
|
|
206
|
+
# Phase 6: Analysis
|
|
207
|
+
print("\n--- Phase 6: Analysis (YouTube) ---")
|
|
208
|
+
|
|
209
|
+
try:
|
|
210
|
+
print(" submitting YouTube URL... (timeout 90s)")
|
|
211
|
+
r = requests.post(f"{BASE_URL}/analyze", headers=HEADERS,
|
|
212
|
+
json={"url": TEST_YT_URL}, timeout=90)
|
|
213
|
+
data = r.json()
|
|
214
|
+
if r.status_code == 200:
|
|
215
|
+
test("6.1 POST /analyze (YouTube)", data.get("success") == True, f"success={data.get('success')}")
|
|
216
|
+
if data.get("data"):
|
|
217
|
+
d = data["data"]
|
|
218
|
+
print(f" title: {d.get('title', 'N/A')[:60]}")
|
|
219
|
+
print(f" category: {d.get('category', 'N/A')}")
|
|
220
|
+
print(f" tags: {str(d.get('tags', [])[:5])}")
|
|
221
|
+
print(f" cached: {data.get('cached', False)}")
|
|
222
|
+
elif r.status_code == 202:
|
|
223
|
+
skip("6.1 POST /analyze (YouTube)", "queued for retry (quota)")
|
|
224
|
+
elif r.status_code == 503:
|
|
225
|
+
skip("6.1 POST /analyze (YouTube)", "503 server busy")
|
|
226
|
+
else:
|
|
227
|
+
test("6.1 POST /analyze (YouTube)", False, f"status={r.status_code}")
|
|
228
|
+
except requests.exceptions.Timeout:
|
|
229
|
+
skip("6.1 POST /analyze (YouTube)", "timed out (90s)")
|
|
230
|
+
except Exception as e:
|
|
231
|
+
test("6.1 POST /analyze (YouTube)", False, str(e))
|
|
232
|
+
|
|
233
|
+
# Summary
|
|
234
|
+
print("\n" + "=" * 50)
|
|
235
|
+
print(f" PASSED: {passed}")
|
|
236
|
+
print(f" FAILED: {failed}")
|
|
237
|
+
print(f" SKIPPED: {skipped}")
|
|
238
|
+
print(f" TOTAL: {passed + failed + skipped}")
|
|
239
|
+
print("=" * 50)
|
|
240
|
+
|
|
241
|
+
sys.exit(1 if failed > 0 else 0)
|
|
File without changes
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
import requests
|
|
3
|
+
import json
|
|
4
|
+
|
|
5
|
+
# Read token
|
|
6
|
+
with open('token.txt', 'r') as f:
|
|
7
|
+
token = f.read().strip()
|
|
8
|
+
|
|
9
|
+
# Test the /recent endpoint
|
|
10
|
+
response = requests.get(
|
|
11
|
+
'http://localhost:5000/recent?limit=10',
|
|
12
|
+
headers={'X-API-Key': token}
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
print(f"Status Code: {response.status_code}")
|
|
16
|
+
print(f"\nResponse:")
|
|
17
|
+
print(json.dumps(response.json(), indent=2))
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
import sys
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
# Ensure backend root is in sys.path
|
|
6
|
+
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
|
|
7
|
+
|
|
8
|
+
from core.database import get_db
|
|
9
|
+
|
|
10
|
+
db = get_db()
|
|
11
|
+
posts = db.get_recent(5)
|
|
12
|
+
print(f'Found {len(posts)} posts in database')
|
|
13
|
+
print()
|
|
14
|
+
|
|
15
|
+
for p in posts:
|
|
16
|
+
print(f"Shortcode: {p.get('shortcode', 'N/A')}")
|
|
17
|
+
print(f"Title: {p.get('title', 'N/A')}")
|
|
18
|
+
print(f"Username: {p.get('username', 'N/A')}")
|
|
19
|
+
print(f"URL: {p.get('url', 'N/A')}")
|
|
20
|
+
print(f"Category: {p.get('category', 'N/A')}")
|
|
21
|
+
print(f"Analyzed: {p.get('analyzed_at', 'N/A')}")
|
|
22
|
+
print("-" * 60)
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Legacy filename retained for compatibility.
|
|
4
|
+
This script now validates API key authentication behavior.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import sys
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
from fastapi.testclient import TestClient
|
|
11
|
+
|
|
12
|
+
# Ensure backend root is in sys.path
|
|
13
|
+
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
|
|
14
|
+
|
|
15
|
+
from api import app, API_TOKEN, generate_api_token # noqa: E402
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
client = TestClient(app)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def test_generate_api_token_shape():
|
|
22
|
+
token = generate_api_token()
|
|
23
|
+
assert isinstance(token, str)
|
|
24
|
+
assert len(token) == 8
|
|
25
|
+
assert token.isalnum()
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def test_ping_works_without_auth():
|
|
29
|
+
response = client.get('/ping')
|
|
30
|
+
assert response.status_code == 200
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def test_queue_status_rejects_invalid_api_key():
|
|
34
|
+
response = client.get('/queue-status', headers={'X-API-Key': 'INVALID_TOKEN'})
|
|
35
|
+
assert response.status_code == 401
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def test_queue_status_accepts_valid_api_key():
|
|
39
|
+
response = client.get('/queue-status', headers={'X-API-Key': API_TOKEN})
|
|
40
|
+
assert response.status_code == 200
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def test_connect_endpoint_is_deprecated():
|
|
44
|
+
response = client.post('/connect', json={'api_key': API_TOKEN})
|
|
45
|
+
assert response.status_code == 410
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
if __name__ == '__main__':
|
|
49
|
+
print('Running API key auth tests...')
|
|
50
|
+
test_generate_api_token_shape()
|
|
51
|
+
print('PASS: API token generation shape')
|
|
52
|
+
|
|
53
|
+
test_ping_works_without_auth()
|
|
54
|
+
print('PASS: /ping unauthenticated access')
|
|
55
|
+
|
|
56
|
+
test_queue_status_rejects_invalid_api_key()
|
|
57
|
+
print('PASS: /queue-status rejects invalid API key')
|
|
58
|
+
|
|
59
|
+
test_queue_status_accepts_valid_api_key()
|
|
60
|
+
print('PASS: /queue-status accepts valid API key')
|
|
61
|
+
|
|
62
|
+
test_connect_endpoint_is_deprecated()
|
|
63
|
+
print('PASS: /connect deprecated behavior')
|
|
64
|
+
|
|
65
|
+
print('\nAll API key auth tests passed!')
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
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!")
|