auto-sync-0240
This commit is contained in:
@@ -1,15 +1,15 @@
|
||||
{
|
||||
"generated_at": "2026-04-20T02:30:01.698166",
|
||||
"generated_at": "2026-04-20T02:40:01.462772",
|
||||
"stats": {
|
||||
"total": 550,
|
||||
"pending": 1061,
|
||||
"total": 552,
|
||||
"pending": 1065,
|
||||
"kaouther_surfaced": 29,
|
||||
"chrome_surfaced": 10,
|
||||
"notif_only_done": 0,
|
||||
"autofix_archived": 0,
|
||||
"cerebras_archived": 0,
|
||||
"older_3d_archived": 0,
|
||||
"unknown": 511,
|
||||
"unknown": 513,
|
||||
"errors": 0
|
||||
},
|
||||
"actions": [
|
||||
|
||||
11
api/blade-tasks/task_20260420003502_c0a58c.json
Normal file
11
api/blade-tasks/task_20260420003502_c0a58c.json
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"id": "task_20260420003502_c0a58c",
|
||||
"name": "Blade self-heal 02:35",
|
||||
"type": "powershell",
|
||||
"command": "\n# Blade self-heal\nWrite-Host \"Self-heal triggered $(Get-Date)\"\n$agentProc = Get-Process powershell | Where-Object { $_.CommandLine -match 'sentinel-agent' }\nif (!$agentProc) {\n Write-Host \"Agent not running, starting...\"\n Start-Process powershell -ArgumentList \"-ExecutionPolicy\",\"Bypass\",\"-File\",\"C:\\ProgramData\\WEVAL\\sentinel-agent.ps1\" -WindowStyle Hidden\n}\n# Clear stale tasks > 3 days locally\n$cutoff = (Get-Date).AddDays(-3)\nGet-ChildItem \"C:\\ProgramData\\WEVAL\\tasks\\*.json\" -ErrorAction SilentlyContinue | Where-Object { $_.LastWriteTime -lt $cutoff } | Move-Item -Destination \"C:\\ProgramData\\WEVAL\\tasks\\archived\\\" -Force -ErrorAction SilentlyContinue\nWrite-Host \"Self-heal complete\"\n",
|
||||
"cmd": "\n# Blade self-heal\nWrite-Host \"Self-heal triggered $(Get-Date)\"\n$agentProc = Get-Process powershell | Where-Object { $_.CommandLine -match 'sentinel-agent' }\nif (!$agentProc) {\n Write-Host \"Agent not running, starting...\"\n Start-Process powershell -ArgumentList \"-ExecutionPolicy\",\"Bypass\",\"-File\",\"C:\\ProgramData\\WEVAL\\sentinel-agent.ps1\" -WindowStyle Hidden\n}\n# Clear stale tasks > 3 days locally\n$cutoff = (Get-Date).AddDays(-3)\nGet-ChildItem \"C:\\ProgramData\\WEVAL\\tasks\\*.json\" -ErrorAction SilentlyContinue | Where-Object { $_.LastWriteTime -lt $cutoff } | Move-Item -Destination \"C:\\ProgramData\\WEVAL\\tasks\\archived\\\" -Force -ErrorAction SilentlyContinue\nWrite-Host \"Self-heal complete\"\n",
|
||||
"priority": "high",
|
||||
"status": "pending",
|
||||
"created": "2026-04-20T00:35:02+00:00",
|
||||
"created_by": "blade-control-ui"
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
<html>
|
||||
<head><title>500 Internal Server Error</title></head>
|
||||
<body>
|
||||
<center><h1>500 Internal Server Error</h1></center>
|
||||
<hr><center>nginx/1.24.0 (Ubuntu)</center>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,186 +1,228 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""Opus v5.9.3: WEVIA auto Resend Full Access key creation via Playwright headless"""
|
||||
import sys, json, os, time, subprocess
|
||||
"""Opus v5.9.4: WEVIA auto Resend Full Access key via Playwright headless."""
|
||||
import sys, json, os, time, subprocess, base64
|
||||
from playwright.sync_api import sync_playwright
|
||||
|
||||
CREDS_FILE = "/opt/wevia-brain/email-providers/resend-creds.json"
|
||||
KEY_FILE = "/opt/wevia-brain/email-providers/resend.key"
|
||||
|
||||
def log(msg):
|
||||
print(f"[{time.strftime('%H:%M:%S')}] {msg}", file=sys.stderr)
|
||||
sys.stderr.write("[{}] {}\n".format(time.strftime("%H:%M:%S"), msg))
|
||||
sys.stderr.flush()
|
||||
|
||||
def save_key(new_key):
|
||||
with open("/tmp/new_resend_key.txt","w") as f:
|
||||
f.write(new_key)
|
||||
subprocess.run(["sudo","cp","/tmp/new_resend_key.txt", KEY_FILE], check=False)
|
||||
subprocess.run(["sudo","chmod","600", KEY_FILE], check=False)
|
||||
b64 = base64.b64encode(new_key.encode()).decode()
|
||||
cmd = "echo " + b64 + " | base64 -d | sudo tee " + KEY_FILE + " > /dev/null && sudo chmod 600 " + KEY_FILE
|
||||
subprocess.run(["curl","-s","-X","POST","https://wevads.weval-consulting.com/api/sentinel-brain.php",
|
||||
"--data-urlencode","action=exec","--data-urlencode","cmd="+cmd,"--max-time","10"],
|
||||
capture_output=True, timeout=15)
|
||||
|
||||
def main():
|
||||
# Get Resend credentials from vault
|
||||
email_vault = "/opt/wevia-brain/email-providers/resend-creds.json"
|
||||
if not os.path.exists(email_vault):
|
||||
return {"ok": False, "error": f"Resend creds missing at {email_vault}. Create with: echo '{{"email":"...","password":"..."}}' | sudo tee {email_vault}"}
|
||||
if not os.path.exists(CREDS_FILE):
|
||||
return {
|
||||
"ok": False,
|
||||
"error": "No creds file at " + CREDS_FILE,
|
||||
"solution": "First type in WEVIA chat: 'set resend password: YOUR_PASSWORD'"
|
||||
}
|
||||
|
||||
with open(email_vault) as f:
|
||||
with open(CREDS_FILE) as f:
|
||||
creds = json.load(f)
|
||||
|
||||
email = creds.get("email", "")
|
||||
password = creds.get("password", "")
|
||||
email = creds.get("email","")
|
||||
password = creds.get("password","")
|
||||
if not email or not password:
|
||||
return {"ok": False, "error": "creds file must have email and password"}
|
||||
return {"ok":False,"error":"creds missing fields"}
|
||||
|
||||
log(f"Login as {email}")
|
||||
|
||||
result = {"ok": False, "steps": []}
|
||||
log("Start Chromium login as " + email)
|
||||
steps = []
|
||||
new_key = None
|
||||
|
||||
with sync_playwright() as p:
|
||||
browser = p.chromium.launch(
|
||||
headless=True,
|
||||
args=["--no-sandbox","--disable-setuid-sandbox","--disable-dev-shm-usage"]
|
||||
args=["--no-sandbox","--disable-setuid-sandbox","--disable-dev-shm-usage","--disable-blink-features=AutomationControlled"]
|
||||
)
|
||||
context = browser.new_context(
|
||||
user_agent="Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36",
|
||||
viewport={"width":1280,"height":800}
|
||||
viewport={"width":1280,"height":900}
|
||||
)
|
||||
page = context.new_page()
|
||||
try:
|
||||
# Step 1: Go to login
|
||||
log("Goto resend.com/login")
|
||||
log("goto /login")
|
||||
page.goto("https://resend.com/login", wait_until="domcontentloaded", timeout=30000)
|
||||
result["steps"].append({"step":"goto_login","url":page.url})
|
||||
page.wait_for_timeout(2000)
|
||||
page.wait_for_timeout(2500)
|
||||
steps.append({"s":"loaded","url":page.url})
|
||||
|
||||
# Step 2: Try Google SSO (most likely since email is gmail)
|
||||
# Or email/password form
|
||||
# Fill email
|
||||
try:
|
||||
# Look for email input
|
||||
email_input = page.locator("input[type=email]").first
|
||||
if email_input.is_visible(timeout=3000):
|
||||
log("Email input found")
|
||||
email_input.fill(email)
|
||||
page.wait_for_timeout(500)
|
||||
# Click continue/next/submit
|
||||
for btn_text in ["Continue","Sign in","Log in","Next"]:
|
||||
try:
|
||||
page.get_by_role("button", name=btn_text).first.click(timeout=2000)
|
||||
log(f"Clicked {btn_text}")
|
||||
break
|
||||
except: pass
|
||||
page.wait_for_timeout(3000)
|
||||
# Password
|
||||
try:
|
||||
pwd_input = page.locator("input[type=password]").first
|
||||
if pwd_input.is_visible(timeout=3000):
|
||||
pwd_input.fill(password)
|
||||
for btn_text in ["Sign in","Log in","Continue","Submit"]:
|
||||
try:
|
||||
page.get_by_role("button", name=btn_text).first.click(timeout=2000)
|
||||
log(f"Clicked {btn_text} (pwd)")
|
||||
break
|
||||
except: pass
|
||||
except:
|
||||
log("No password field - maybe magic link?")
|
||||
page.fill("input[type=email]", email, timeout=5000)
|
||||
log("email filled")
|
||||
except Exception as e:
|
||||
log(f"Email flow err: {e}")
|
||||
log("no email input: " + str(e)[:100])
|
||||
|
||||
page.wait_for_timeout(5000)
|
||||
result["steps"].append({"step":"after_login_attempt","url":page.url,"title":page.title()})
|
||||
# Submit email (look for Continue button)
|
||||
submitted = False
|
||||
for txt in ["Continue","Log in","Sign in","Next"]:
|
||||
try:
|
||||
page.get_by_role("button", name=txt, exact=False).first.click(timeout=2500)
|
||||
log("clicked " + txt)
|
||||
submitted = True
|
||||
break
|
||||
except:
|
||||
continue
|
||||
if not submitted:
|
||||
try:
|
||||
page.keyboard.press("Enter")
|
||||
log("pressed enter")
|
||||
except:
|
||||
pass
|
||||
|
||||
# Screenshot for debug
|
||||
page.screenshot(path="/tmp/resend-login-debug.png", full_page=True)
|
||||
page.wait_for_timeout(3500)
|
||||
steps.append({"s":"after_email","url":page.url})
|
||||
|
||||
if "login" in page.url.lower() or "signin" in page.url.lower():
|
||||
# Try password if field appears
|
||||
try:
|
||||
if page.locator("input[type=password]").first.is_visible(timeout=3000):
|
||||
page.fill("input[type=password]", password)
|
||||
log("password filled")
|
||||
for txt in ["Log in","Sign in","Continue","Submit"]:
|
||||
try:
|
||||
page.get_by_role("button", name=txt, exact=False).first.click(timeout=2500)
|
||||
break
|
||||
except:
|
||||
continue
|
||||
except:
|
||||
log("no password field - probably magic link flow")
|
||||
page.screenshot(path="/tmp/resend-login-no-pwd.png", full_page=True)
|
||||
browser.close()
|
||||
return {
|
||||
"ok": False,
|
||||
"error": "Still on login page after submit - likely need magic link or 2FA",
|
||||
"error": "Resend uses magic-link login (no password field detected)",
|
||||
"url": page.url,
|
||||
"screenshot": "/tmp/resend-login-debug.png",
|
||||
"steps": result["steps"],
|
||||
"suggestion": "Use email magic link manually OR provide 2FA code OR use password manager credentials"
|
||||
"screenshot": "/tmp/resend-login-no-pwd.png",
|
||||
"steps": steps,
|
||||
"workaround": "Resend sent magic link to " + email + ". Options: (a) manually open link in browser, (b) auto-read Gmail via Gmail API and follow link (requires Gmail OAuth)"
|
||||
}
|
||||
|
||||
# Step 3: Go to api-keys page
|
||||
log("Goto /api-keys")
|
||||
page.wait_for_timeout(5000)
|
||||
steps.append({"s":"after_pwd","url":page.url})
|
||||
|
||||
if "login" in page.url.lower() or "signin" in page.url.lower():
|
||||
page.screenshot(path="/tmp/resend-still-login.png", full_page=True)
|
||||
browser.close()
|
||||
return {"ok": False, "error":"still on login", "url":page.url, "screenshot":"/tmp/resend-still-login.png", "steps": steps}
|
||||
|
||||
# Navigate to API keys
|
||||
log("goto /api-keys")
|
||||
page.goto("https://resend.com/api-keys", wait_until="domcontentloaded", timeout=20000)
|
||||
page.wait_for_timeout(3000)
|
||||
steps.append({"s":"apikeys_loaded","url":page.url})
|
||||
|
||||
# Step 4: Click "Create API Key"
|
||||
for sel in ["text=Create API Key","text=Create API key","button:has-text(\"Create\")"]:
|
||||
# Click Create API Key
|
||||
clicked = False
|
||||
for sel in ["text=Create API Key","text=Create API key","text=Create key"]:
|
||||
try:
|
||||
page.locator(sel).first.click(timeout=3000)
|
||||
log(f"Clicked: {sel}")
|
||||
clicked = True
|
||||
log("clicked create: " + sel)
|
||||
break
|
||||
except: pass
|
||||
except:
|
||||
continue
|
||||
if not clicked:
|
||||
try:
|
||||
page.get_by_role("button", name="Create", exact=False).first.click(timeout=3000)
|
||||
clicked = True
|
||||
except:
|
||||
pass
|
||||
|
||||
page.wait_for_timeout(2000)
|
||||
|
||||
# Step 5: Fill name
|
||||
# Fill name
|
||||
try:
|
||||
page.locator("input[placeholder*=\"ame\"], input[name=name]").first.fill("wevia-master-full-auto")
|
||||
except: pass
|
||||
page.locator("input[name=name], input[placeholder*=ame]").first.fill("wevia-master-auto-"+str(int(time.time())))
|
||||
log("name filled")
|
||||
except:
|
||||
pass
|
||||
|
||||
# Step 6: Select Full access
|
||||
# Select Full access radio
|
||||
try:
|
||||
page.locator("text=Full access").first.click(timeout=3000)
|
||||
page.get_by_text("Full access", exact=False).first.click(timeout=3000)
|
||||
log("selected Full access")
|
||||
except:
|
||||
try:
|
||||
page.get_by_role("radio").nth(0).click() # first radio often = Full
|
||||
except: pass
|
||||
radios = page.locator("input[type=radio]").all()
|
||||
if radios:
|
||||
radios[0].click()
|
||||
log("clicked first radio")
|
||||
except:
|
||||
pass
|
||||
|
||||
# Step 7: Click create
|
||||
for sel in ["text=Create","button:has-text(\"Create\")","button[type=submit]"]:
|
||||
# Click final Add/Create button
|
||||
for txt in ["Add","Create","Save","Generate"]:
|
||||
try:
|
||||
page.locator(sel).first.click(timeout=3000)
|
||||
log(f"Clicked final create: {sel}")
|
||||
page.get_by_role("button", name=txt, exact=False).first.click(timeout=2500)
|
||||
log("final click: " + txt)
|
||||
break
|
||||
except: pass
|
||||
except:
|
||||
continue
|
||||
|
||||
page.wait_for_timeout(4000)
|
||||
page.wait_for_timeout(4500)
|
||||
page.screenshot(path="/tmp/resend-key-shown.png", full_page=True)
|
||||
|
||||
# Step 8: Extract the key from page (monospace field or copy button aria)
|
||||
key_selectors = ["code","input[value^=re_]","[aria-label*=API]","pre"]
|
||||
for sel in key_selectors:
|
||||
# Extract key
|
||||
for sel in ["code","pre","[class*=mono]","input[readonly]"]:
|
||||
try:
|
||||
elts = page.locator(sel).all()
|
||||
for e in elts:
|
||||
txt = (e.inner_text() or "")
|
||||
if txt.startswith("re_") and len(txt) > 20:
|
||||
new_key = txt.strip()
|
||||
break
|
||||
val = e.get_attribute("value") or ""
|
||||
if val.startswith("re_") and len(val) > 20:
|
||||
new_key = val.strip()
|
||||
break
|
||||
if new_key: break
|
||||
except: pass
|
||||
|
||||
page.screenshot(path="/tmp/resend-key-created.png", full_page=True)
|
||||
result["steps"].append({"step":"key_extraction","found":bool(new_key)})
|
||||
try:
|
||||
txt = e.inner_text(timeout=1000) or ""
|
||||
if txt.startswith("re_") and len(txt) >= 20:
|
||||
new_key = txt.strip()
|
||||
log("found key in " + sel)
|
||||
break
|
||||
val = e.get_attribute("value") or ""
|
||||
if val.startswith("re_") and len(val) >= 20:
|
||||
new_key = val.strip()
|
||||
break
|
||||
except:
|
||||
continue
|
||||
if new_key:
|
||||
break
|
||||
except:
|
||||
continue
|
||||
|
||||
steps.append({"s":"key_extraction","found": bool(new_key)})
|
||||
browser.close()
|
||||
except Exception as e:
|
||||
try:
|
||||
page.screenshot(path="/tmp/resend-error.png", full_page=True)
|
||||
except:
|
||||
pass
|
||||
browser.close()
|
||||
return {"ok":False,"error":str(e),"steps":result["steps"]}
|
||||
return {"ok":False,"error":str(e)[:300],"steps":steps,"screenshot":"/tmp/resend-error.png"}
|
||||
|
||||
if not new_key:
|
||||
return {"ok": False, "error": "Could not extract new key", "steps": result["steps"], "screenshot": "/tmp/resend-key-created.png"}
|
||||
|
||||
# Save key on S204 + S95
|
||||
with open("/tmp/new_resend_key.txt","w") as f:
|
||||
f.write(new_key)
|
||||
subprocess.run(["sudo","cp","/tmp/new_resend_key.txt","/opt/wevia-brain/email-providers/resend.key"], check=False)
|
||||
subprocess.run(["sudo","chmod","600","/opt/wevia-brain/email-providers/resend.key"], check=False)
|
||||
|
||||
import base64
|
||||
b64 = base64.b64encode(new_key.encode()).decode()
|
||||
cmd = f"echo {b64} | base64 -d | sudo tee /opt/wevia-brain/email-providers/resend.key > /dev/null && sudo chmod 600 /opt/wevia-brain/email-providers/resend.key"
|
||||
subprocess.run(["curl","-s","-X","POST","https://wevads.weval-consulting.com/api/sentinel-brain.php",
|
||||
"--data-urlencode","action=exec",
|
||||
"--data-urlencode",f"cmd={cmd}","--max-time","10"], capture_output=True, timeout=15)
|
||||
return {"ok": False, "error":"key extraction failed", "steps":steps, "screenshot":"/tmp/resend-key-shown.png"}
|
||||
|
||||
save_key(new_key)
|
||||
return {
|
||||
"ok": True,
|
||||
"v": "V5.9.3-playwright-auto-resend-key",
|
||||
"key_preview": new_key[:10] + "..." + new_key[-4:],
|
||||
"key_saved_s204": True,
|
||||
"key_saved_s95": True,
|
||||
"screenshot": "/tmp/resend-key-created.png",
|
||||
"next_step": "Now call resend_ultimate_setup which uses this new key automatically"
|
||||
"v": "V5.9.4-playwright-full-auto",
|
||||
"key_preview": new_key[:10]+"..."+new_key[-4:],
|
||||
"saved_dual": True,
|
||||
"screenshot": "/tmp/resend-key-shown.png",
|
||||
"steps": steps,
|
||||
"next_step": "Now trigger: 'resend full setup' to add wevup.app domain + DNS records"
|
||||
}
|
||||
|
||||
if __name__ == "__main__":
|
||||
r = main()
|
||||
print(json.dumps(r, indent=2))
|
||||
try:
|
||||
r = main()
|
||||
except Exception as e:
|
||||
r = {"ok": False, "error": "fatal: " + str(e)[:300]}
|
||||
sys.stdout.write(json.dumps(r, indent=2))
|
||||
sys.stdout.flush()
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"ok": true,
|
||||
"version": "V83-business-kpi",
|
||||
"ts": "2026-04-20T00:31:03+00:00",
|
||||
"ts": "2026-04-20T00:35:14+00:00",
|
||||
"summary": {
|
||||
"total_categories": 7,
|
||||
"total_kpis": 56,
|
||||
|
||||
Reference in New Issue
Block a user