feat(onboarding): propagate NEEDS_SIGNUP (code 4) through onboard-driver and muse-signin
This commit is contained in:
+1
-1
@@ -16,7 +16,7 @@ node name, chrome-box profile, API `--account`, and the agent's display name.
|
|||||||
| email | Email used for login (if email_otp). Never store passwords. |
|
| email | Email used for login (if email_otp). Never store passwords. |
|
||||||
| phone_otp | `yes` if phone OTP was used. Never store the phone number. |
|
| phone_otp | `yes` if phone OTP was used. Never store the phone number. |
|
||||||
| instagram_linked | `yes`/`no`/`unknown` — whether Meta account has IG linked |
|
| instagram_linked | `yes`/`no`/`unknown` — whether Meta account has IG linked |
|
||||||
| status | `active`, `pending_auth`, `expired`, `disabled` |
|
| status | `active`, `pending_auth`, `needs_signup`, `expired`, `disabled` |
|
||||||
| egress_ip | Current WARP egress IP for the node |
|
| egress_ip | Current WARP egress IP for the node |
|
||||||
| cdp_port | CDP port for the browser |
|
| cdp_port | CDP port for the browser |
|
||||||
| display_name | Agent's chosen display name in muse.ai (may differ from `agent`) |
|
| display_name | Agent's chosen display name in muse.ai (may differ from `agent`) |
|
||||||
|
|||||||
@@ -15,6 +15,13 @@ The operator then obtains the OTP (via chat with user) and re-runs:
|
|||||||
|
|
||||||
This keeps credentials out of the automation — the OTP is provided
|
This keeps credentials out of the automation — the OTP is provided
|
||||||
transiently via the operator, never stored.
|
transiently via the operator, never stored.
|
||||||
|
|
||||||
|
Exit codes:
|
||||||
|
0: Success / already logged in
|
||||||
|
2: APPROVAL_NEEDED (OTP prompt reached, awaiting code)
|
||||||
|
3: NEEDS_HUMAN (multi-account selection needs manual choice)
|
||||||
|
4: NEEDS_SIGNUP (account not found / registration required)
|
||||||
|
1: General error
|
||||||
"""
|
"""
|
||||||
import json, urllib.request, websocket, time, sys, argparse, importlib.util
|
import json, urllib.request, websocket, time, sys, argparse, importlib.util
|
||||||
|
|
||||||
@@ -199,6 +206,35 @@ def main():
|
|||||||
ws.close()
|
ws.close()
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
else:
|
else:
|
||||||
|
body_full = ev(ws, "document.body.innerText.slice(0,2000)") or ""
|
||||||
|
body_lower = body_full.lower()
|
||||||
|
signup_cues = [
|
||||||
|
"no account found",
|
||||||
|
"couldn't find your account",
|
||||||
|
"cannot find",
|
||||||
|
"create an account",
|
||||||
|
"create your account",
|
||||||
|
"create account",
|
||||||
|
"sign up",
|
||||||
|
"sign-up",
|
||||||
|
"doesn't have an account",
|
||||||
|
"not registered",
|
||||||
|
"register",
|
||||||
|
"get started"
|
||||||
|
]
|
||||||
|
has_signup_cue = any(cue in body_lower for cue in signup_cues)
|
||||||
|
has_name_or_pwd = ev(ws, """(()=>{
|
||||||
|
const inps = [...document.querySelectorAll('input')];
|
||||||
|
return inps.some(i => (i.placeholder && i.placeholder.toLowerCase().includes('name')) ||
|
||||||
|
(i.name && i.name.toLowerCase().includes('name')) ||
|
||||||
|
(i.type === 'password'));
|
||||||
|
})()""")
|
||||||
|
|
||||||
|
if has_signup_cue or has_name_or_pwd:
|
||||||
|
print(f"NEEDS_SIGNUP: Account does not exist on Muse for {args.email}. Client must sign up first at https://muse.ai", file=sys.stderr)
|
||||||
|
ws.close()
|
||||||
|
sys.exit(4)
|
||||||
|
|
||||||
print("No OTP prompt found - check page state", file=sys.stderr)
|
print("No OTP prompt found - check page state", file=sys.stderr)
|
||||||
print(f"Body: {body[:200]}", file=sys.stderr)
|
print(f"Body: {body[:200]}", file=sys.stderr)
|
||||||
ws.close()
|
ws.close()
|
||||||
|
|||||||
+29
-6
@@ -8,7 +8,9 @@ Runs INSIDE the node's netns (via netvm-exec.sh). Reads the identifier
|
|||||||
onboard-driver.py --node muse --service muse --id-type email --step submit
|
onboard-driver.py --node muse --service muse --id-type email --step submit
|
||||||
|
|
||||||
Exit codes: 0 = step done, 2 = APPROVAL_NEEDED (code sent, awaiting OTP),
|
Exit codes: 0 = step done, 2 = APPROVAL_NEEDED (code sent, awaiting OTP),
|
||||||
3 = NEEDS_HUMAN (multi-account selection needs a person), 1 = failed.
|
3 = NEEDS_HUMAN (multi-account selection needs a person),
|
||||||
|
4 = NEEDS_SIGNUP (unregistered client email — client must sign up first),
|
||||||
|
1 = failed.
|
||||||
The identifier/code are passed to the local signin script as
|
The identifier/code are passed to the local signin script as
|
||||||
argv (transient, same trust domain — bl is operator infrastructure);
|
argv (transient, same trust domain — bl is operator infrastructure);
|
||||||
they never cross a network boundary except inside the already-encrypted
|
they never cross a network boundary except inside the already-encrypted
|
||||||
@@ -17,6 +19,7 @@ VM->bl SSH stdin pipe.
|
|||||||
Part of the cred onboarding module (front-door repo, docs/CRED-MODULE.md).
|
Part of the cred onboarding module (front-door repo, docs/CRED-MODULE.md).
|
||||||
"""
|
"""
|
||||||
import argparse
|
import argparse
|
||||||
|
import datetime
|
||||||
import importlib.util
|
import importlib.util
|
||||||
import json
|
import json
|
||||||
import subprocess
|
import subprocess
|
||||||
@@ -54,10 +57,6 @@ def main():
|
|||||||
help="display-name hint for multi-account selection")
|
help="display-name hint for multi-account selection")
|
||||||
args = p.parse_args()
|
args = p.parse_args()
|
||||||
|
|
||||||
lines = sys.stdin.read().splitlines()
|
|
||||||
identifier = lines[0].strip() if lines else ""
|
|
||||||
code = lines[1].strip() if len(lines) > 1 else ""
|
|
||||||
|
|
||||||
if args.service != "muse" or args.id_type != "email":
|
if args.service != "muse" or args.id_type != "email":
|
||||||
print("ERROR: unsupported service/id_type "
|
print("ERROR: unsupported service/id_type "
|
||||||
"(muse+email only for now)", file=sys.stderr)
|
"(muse+email only for now)", file=sys.stderr)
|
||||||
@@ -77,6 +76,11 @@ def main():
|
|||||||
print("ERROR: CDP unreachable on %s" % port, file=sys.stderr)
|
print("ERROR: CDP unreachable on %s" % port, file=sys.stderr)
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
|
lines = sys.stdin.read().splitlines()
|
||||||
|
identifier = lines[0].strip() if lines else ""
|
||||||
|
code = lines[1].strip() if len(lines) > 1 else ""
|
||||||
|
|
||||||
|
|
||||||
if not identifier:
|
if not identifier:
|
||||||
print("ERROR: no identifier on stdin", file=sys.stderr)
|
print("ERROR: no identifier on stdin", file=sys.stderr)
|
||||||
return 1
|
return 1
|
||||||
@@ -90,9 +94,28 @@ def main():
|
|||||||
return 1
|
return 1
|
||||||
cmd += ["--otp", code]
|
cmd += ["--otp", code]
|
||||||
r = subprocess.run(cmd, capture_output=True, text=True, timeout=220)
|
r = subprocess.run(cmd, capture_output=True, text=True, timeout=220)
|
||||||
# Propagate the signin script's contract: 2 = OTP prompt reached.
|
# Propagate the signin script's contract:
|
||||||
|
# 0 = done, 2 = OTP prompt reached, 3 = NEEDS_HUMAN, 4 = NEEDS_SIGNUP
|
||||||
sys.stdout.write(r.stdout)
|
sys.stdout.write(r.stdout)
|
||||||
sys.stderr.write(r.stderr)
|
sys.stderr.write(r.stderr)
|
||||||
|
|
||||||
|
if r.returncode == 4:
|
||||||
|
log_entry = {
|
||||||
|
"ts": datetime.datetime.now(datetime.timezone.utc).isoformat(),
|
||||||
|
"type": "onboarding_needs_signup",
|
||||||
|
"node": args.node,
|
||||||
|
"service": args.service,
|
||||||
|
"email": identifier,
|
||||||
|
"status": "needs_signup",
|
||||||
|
"action_required": "ask_client_to_sign_up",
|
||||||
|
"signup_url": "https://muse.ai"
|
||||||
|
}
|
||||||
|
try:
|
||||||
|
with open("/home/super/Projects/NetVM/job-log.jsonl", "a") as f:
|
||||||
|
f.write(json.dumps(log_entry) + "\n")
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
return r.returncode
|
return r.returncode
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -401,6 +401,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</footer>
|
</footer>
|
||||||
|
|
||||||
<script src="box.js"></script>
|
<script src="box.js?v=20261004b"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
Reference in New Issue
Block a user