From 027d7baa30c2d7ac301f9896d6bf8dfa41b219f4 Mon Sep 17 00:00:00 2001 From: operator Date: Sat, 3 Oct 2026 21:30:32 +0000 Subject: [PATCH] multi-account selection: --account-name hint, exit 3 NEEDS_HUMAN onboard-driver.py accepts --account-name and forwards to muse-signin.py. muse-signin.py detects Meta multi-account selection after OTP submit: with a hint it clicks the matching display-name button (never the +1 collapsed entry); without a hint (or no match) it exits 3 so the queue marks the job needs_human instead of stalling. --- bin/muse-signin.py | 38 ++++++++++++++++++++++++++++++++++++++ bin/onboard-driver.py | 7 ++++++- 2 files changed, 44 insertions(+), 1 deletion(-) diff --git a/bin/muse-signin.py b/bin/muse-signin.py index 8b28d8c..93692ad 100755 --- a/bin/muse-signin.py +++ b/bin/muse-signin.py @@ -58,6 +58,9 @@ def main(): help='node name: CDP port comes from the NODES.md registry') p.add_argument('--cdp-port', default=None, help='explicit CDP port (overrides --node lookup)') + p.add_argument('--account-name', default=None, + help='display-name hint for Meta multi-account selection ' + '(never the "+1" entry)') args = p.parse_args() if args.cdp_port: @@ -155,6 +158,41 @@ def main(): print(f"SUCCESS: Logged in as {args.email} (title: {title})") ws.close() return 0 + # Multi-account selection: Meta shows one button per matching + # account plus a "+1" collapsed entry. The "+1" is NOT a real + # account — click the button whose text contains the hinted + # display name. Without a hint (or no match), a human must pick. + if "Your email matches multiple accounts" in ev( + ws, "document.body.innerText.slice(0,2000)"): + if not args.account_name: + print("NEEDS_HUMAN: multiple accounts match and no " + "--account-name hint was given", file=sys.stderr) + ws.close() + sys.exit(3) + picked = ev(ws, """(async()=>{ + const want=%s.toLowerCase(); + const btns=[...document.querySelectorAll('button')]; + const t=btns.find(b=>b.innerText.toLowerCase().includes(want) + && !b.innerText.includes('+1')); + if(t){t.click();return true;} return false; + })()""" % json.dumps(args.account_name), True) + if not picked: + print(f"NEEDS_HUMAN: no account button matched " + f"'{args.account_name}'", file=sys.stderr) + ws.close() + sys.exit(3) + print(f"Selected account '{args.account_name}', waiting...") + time.sleep(5) + body = ev(ws, "document.body.innerText.slice(0,200)") + if "Connected" in body or "Chats" in body: + print(f"SUCCESS: Logged in as {args.email} " + f"(account: {args.account_name})") + ws.close() + return 0 + print("WARNING: account selection did not land in chat", + file=sys.stderr) + ws.close() + sys.exit(1) else: print(f"WARNING: Login may have failed. Title: {title}", file=sys.stderr) print(f"Body: {body[:100]}", file=sys.stderr) diff --git a/bin/onboard-driver.py b/bin/onboard-driver.py index 7df9b76..7794c49 100755 --- a/bin/onboard-driver.py +++ b/bin/onboard-driver.py @@ -8,7 +8,8 @@ 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 Exit codes: 0 = step done, 2 = APPROVAL_NEEDED (code sent, awaiting OTP), -1 = failed. The identifier/code are passed to the local signin script as +3 = NEEDS_HUMAN (multi-account selection needs a person), 1 = failed. +The identifier/code are passed to the local signin script as argv (transient, same trust domain — bl is operator infrastructure); they never cross a network boundary except inside the already-encrypted VM->bl SSH stdin pipe. @@ -49,6 +50,8 @@ def main(): p.add_argument("--id-type", required=True) p.add_argument("--step", required=True, choices=["initiate", "submit"]) p.add_argument("--dry-run", action="store_true") + p.add_argument("--account-name", default=None, + help="display-name hint for multi-account selection") args = p.parse_args() lines = sys.stdin.read().splitlines() @@ -79,6 +82,8 @@ def main(): return 1 cmd = [sys.executable, SIGNIN, "--node", args.node, "--email", identifier] + if args.account_name: + cmd += ["--account-name", args.account_name] if args.step == "submit": if not code: print("ERROR: no code on stdin", file=sys.stderr)