app
Fusion - the Fusion 360 Bridge
Public Made by Adomby adom
Drive Autodesk Fusion 360 from the cloud via Adom Bridge: component libraries, IPC package generation, board layout, exports (STEP/Gerbers/BOM/CPL), fast APS cloud search, and parametric modeling.
← Commit history
v1.9.32: self-driving sign-in to the exact human wall
6 files changed
+146−10
BRIDGE_VERSION+1−1@@ -1 +1 @@-1.9.31+1.9.32
adom-bridge-fusion-manifest.json+4−4@@ -1,10 +1,10 @@ { "manifest_version": 1, "name": "fusion360",- "version": "1.9.31",- "url": "https://wiki.adom.inc/download/adom/fusion-bridge/1.9.31/adom-bridge-fusion-v1.9.31.zip",- "sha256": "747295223d124465a0a477f6f3e01b8ceaedd9e0e16bae78fba1cf4ab246db52",- "size": 381112,+ "version": "1.9.32",+ "url": "https://wiki.adom.inc/download/adom/fusion-bridge/1.9.32/adom-bridge-fusion-v1.9.32.zip",+ "sha256": "d0c532dafa7ae77bad1048c70bf375a7ae8bf5c4379211ebb4ca0f2eb99df076",+ "size": 383092, "verbPrefixes": [ "fusion_" ],
bridge.json+1−1@@ -2,7 +2,7 @@ "manifest_version": 1, "name": "fusion360", "displayName": "Autodesk Fusion 360",- "version": "1.9.31",+ "version": "1.9.32", "description": "Drive Autodesk Fusion 360 from the cloud: launch Fusion, electronics board layout, design rules, exports (STEP/IGES/STL/3MF/USDZ/OBJ/DXF/DWG/Gerbers/BOM/CPL), fast APS server-indexed cloud search plus browse/recent/file-info/versions, cloud file download/upload and folder creation, and in-app parametric modeling (fusion_run_modeling_script). Never-charge: APS calls are capped to the free tier.", "homepage": "https://wiki.adom.inc/adom/fusion-bridge", "author": "Adom Inc.",
describe.py+1−1@@ -37,7 +37,7 @@ _T = [ {"success": "bool", "targets": "list"}, 20, None, False, {"title": "Fusion needs you", "body": "Please complete the Autodesk 2FA prompt on winvm - everything else is done."}), - ("fusion_signin", "Sign Fusion in through the CORRECT browser profile - the fix for Fusion's dumb OAuth that fires at the OS-DEFAULT browser and lands in the wrong Autodesk account. Fusion opens its sign-in in whatever browser is default (e.g. a personal Chrome), but a user's Autodesk seat often lives in a different profile (work). This verb reads the FULL authorize URL (client_id/PKCE/state/request_id) that Fusion wrote into that wrong browser's HISTORY and re-opens it in the RIGHT profile IN THE BACKGROUND; the URL completes back to the running Fusion via the idmgr/callback + autodesk:// handoff, so the browser no longer has to be the OS default. Pass {profile:'chrome:[email protected]'} to fix + remember your Autodesk profile; else it probes each profile for a live Autodesk session (or asks). Staged: if Fusion hasn't opened the browser yet it returns stage:'click_signin' so you click Fusion's Sign In first. BEST: pass {auto:true} to run the WHOLE chain in ONE server-side call - it clicks Fusion's Sign In, polls the browser history on-box for the fresh authorize URL, and re-opens it in the target profile, all without extra remote round-trips, so it fits inside Fusion's ~2-min OAuth request expiry even on a slow/flaky relay (the manual staged path is the fallback). Fusion's request EXPIRES in ~2 min, so a stale captured URL returns stage:'stale' - restart Fusion (fusion_stop/start) for a fresh request and retry auto PROMPTLY. See the 'fusion-multiprofile-signin' skill.",+ ("fusion_signin", "SELF-DRIVING as of 1.9.32: launches Fusion if needed, clicks Sign In, opens the OAuth in the right profile, TYPES THE EMAIL and advances to the exact remaining wall (password/2FA/handoff), toasting the user at that step only - see signinWall + _hint in the response, and the fusion-autodesk-signin skill. Sign Fusion in through the CORRECT browser profile - the fix for Fusion's dumb OAuth that fires at the OS-DEFAULT browser and lands in the wrong Autodesk account. Fusion opens its sign-in in whatever browser is default (e.g. a personal Chrome), but a user's Autodesk seat often lives in a different profile (work). This verb reads the FULL authorize URL (client_id/PKCE/state/request_id) that Fusion wrote into that wrong browser's HISTORY and re-opens it in the RIGHT profile IN THE BACKGROUND; the URL completes back to the running Fusion via the idmgr/callback + autodesk:// handoff, so the browser no longer has to be the OS default. Pass {profile:'chrome:[email protected]'} to fix + remember your Autodesk profile; else it probes each profile for a live Autodesk session (or asks). Staged: if Fusion hasn't opened the browser yet it returns stage:'click_signin' so you click Fusion's Sign In first. BEST: pass {auto:true} to run the WHOLE chain in ONE server-side call - it clicks Fusion's Sign In, polls the browser history on-box for the fresh authorize URL, and re-opens it in the target profile, all without extra remote round-trips, so it fits inside Fusion's ~2-min OAuth request expiry even on a slow/flaky relay (the manual staged path is the fallback). Fusion's request EXPIRES in ~2 min, so a stale captured URL returns stage:'stale' - restart Fusion (fusion_stop/start) for a fresh request and retry auto PROMPTLY. See the 'fusion-multiprofile-signin' skill.", {"profile": "optional str (chrome:<email>/edge:<email> - the profile your Autodesk account is in; remembered)", "auto": "optional bool (do click+capture+reopen in one on-box call - recommended)", "waitSec": "optional int (server-side poll budget for the fresh URL, default 32)"}, {"stage": "str", "done": "bool", "signinWhere": "str", "signinProfile": "str", "signinProfileReason": "str", "signinWrongBrowser": "str", "signinAuthUrl": "str", "signinOpenedVia": "str (extension|extension_free)", "signinWall": "str|null (twofactor_email_code|credentials|protocol_handoff)", "notifyDelivered": "bool", "signinWallHint": "str|null", "narrate": "str"}, 120, "fusion_signin", True, {}),
server.py+112−3@@ -4796,6 +4796,54 @@ def _autodesk_window_hwnds() -> set: return out +def _advance_credentials_page(hwnd, email) -> dict:+ """Self-drive the Autodesk credentials page as far as possible WITHOUT secrets+ (John, 2026-08-16: 'do as much self driving automation as you can... only let the+ AI know when you need it to know'). Background UIA only - no foreground, no focus+ steal: set the email/username field, invoke Next/Sign in, then classify what the+ page became. Returns {advanced, wall, detail}. NEVER touches password fields."""+ import time as _t+ out = {"advanced": False, "wall": "credentials", "detail": ""}+ try:+ found = _unwrap(_ad_call("desktop_find_control", {"hwnd": int(hwnd), "role": "edit"}, timeout=40))+ ctrls = found.get("controls") or found.get("matches") or []+ if not ctrls and found.get("best"):+ ctrls = [found["best"]]+ pwd = [c for c in ctrls if "password" in str(c.get("name", "")).lower()+ or "password" in str(c.get("automationId", "")).lower()]+ if pwd:+ out["wall"] = "password"+ out["detail"] = "password field present - the human step"+ return out+ emailish = [c for c in ctrls if any(k in (str(c.get("name", "")) + str(c.get("automationId", ""))).lower()+ for k in ("email", "username", "user id", "userid"))] or ctrls[:1]+ if email and emailish:+ c = emailish[0]+ r = _unwrap(_ad_call("desktop_ui_set", {"hwnd": int(hwnd),+ "automationId": c.get("automationId"),+ "name": c.get("name"), "value": email}, timeout=30))+ if r.get("success") or r.get("ok"):+ out["detail"] = "typed email %r into %r (background UIA)" % (email, c.get("name") or c.get("automationId"))+ for btn in ("Next", "NEXT", "Sign in", "Sign In", "Continue"):+ rb = _unwrap(_ad_call("desktop_ui_click", {"hwnd": int(hwnd), "name": btn}, timeout=25))+ if rb.get("success") or rb.get("ok") or rb.get("status") == "ok":+ out["advanced"] = True+ out["detail"] += "; invoked %r" % btn+ break+ if out["advanced"]:+ _t.sleep(4)+ found2 = _unwrap(_ad_call("desktop_find_control", {"hwnd": int(hwnd), "role": "edit"}, timeout=40))+ ctrls2 = found2.get("controls") or found2.get("matches") or []+ if not ctrls2 and found2.get("best"):+ ctrls2 = [found2["best"]]+ if any("password" in (str(c.get("name", "")) + str(c.get("automationId", ""))).lower() for c in ctrls2):+ out["wall"] = "password"+ out["detail"] += "; page advanced to the PASSWORD step"+ except Exception as e:+ out["detail"] = "advance error: " + str(e)[:120]+ return out++ def _detect_signin_wall(ignore_hwnds=None) -> dict: """Classify what the Autodesk sign-in is waiting on, from BROWSER WINDOW TITLES. Title-based on purpose: cheap, needs no OCR, and never touches/foregrounds the window.@@ -4827,12 +4875,29 @@ _WALL_COPY = { "credentials": ("Autodesk sign-in needs you", "The Autodesk sign-in is asking for credentials. I never type your password " "or 2FA - please complete it in the Chrome window."),+ "password": ("Just your password, everything else is done",+ "Adom drove the Fusion sign-in all the way: email entered, page advanced. "+ "The ONLY step left is your password (and I never type secrets). Type it in "+ "the browser window and everything continues automatically."), "protocol_handoff": ("Allow Fusion to finish signing in", "Chrome is asking permission to open Autodesk Identity Manager. Click " "Open to hand the login back to Fusion."), } +def _resolve_and_advance_wall(wall_info, email) -> dict:+ """Given a detected wall, push it as far as code can go (email typing on the+ credentials page), refining 'credentials' into 'password' when that is what+ remains. Returns possibly-updated wall_info with 'advance' detail attached."""+ if wall_info.get("wall") == "credentials" and wall_info.get("hwnd"):+ adv = _advance_credentials_page(wall_info["hwnd"], email)+ wall_info = dict(wall_info)+ wall_info["wall"] = adv.get("wall") or wall_info["wall"]+ wall_info["advance"] = adv.get("detail")+ wall_info["advanced"] = adv.get("advanced")+ return wall_info++ def _notify_signin_wall(wall: str) -> dict: """Toast the user's real desktop about a human wall. Best-effort, never raises.""" title, body = _WALL_COPY.get(wall, ("Fusion sign-in needs you", "Please check the sign-in window."))@@ -4855,6 +4920,14 @@ def _wall_hint(wall: str, notified: bool) -> str: "NOT installed, TELL THE USER that installing adom-google would let you finish " "logins like this automatically next time, then let them type the code. NEVER ask " "them for the code if you can read it yourself.")+ if wall == "password":+ return (common ++ "WALL: PASSWORD ONLY. The bridge already typed the user's email and advanced the "+ "page (see steps[]) - the single remaining human act is typing their password in "+ "the browser window. Do NOT ask them for the password, do NOT try to type it, and "+ "do NOT re-drive the page. After they type it, the flow continues automatically - "+ "poll fusion_readiness until ready:true (a 2FA wall may follow; if it does you may "+ "be able to finish it yourself via fusion_signin_2fa + adom-google Gmail).") if wall == "protocol_handoff": return (common + "WALL: Chrome's native 'Open Autodesk Identity Manager?' dialog. Try to clear it "@@ -5092,9 +5165,25 @@ def _orchestrate_signin(args: dict) -> dict: rd = _handle_fusion_readiness({}, {}) if not rd.get("running"):- return {"success": True, "stage": "not_running", "done": False, "steps": steps,- "_hint": "Fusion is not running. fusion_start, then fusion_signin.",- "statusVerb": "fusion_signin"}+ # Self-drive (John, 2026-08-16): never hand "go start Fusion" back to the caller.+ steps.append("Fusion was not running - launching it (background) as part of sign-in")+ try:+ dispatch_command("start", {})+ except Exception as e:+ steps.append("launch failed: " + str(e)[:80])+ import time as _lt+ deadline = _lt.time() + 120+ while _lt.time() < deadline:+ _lt.sleep(8)+ rd = _handle_fusion_readiness({}, {})+ if rd.get("running"):+ break+ if not rd.get("running"):+ return {"success": False, "stage": "launch_failed", "done": False, "steps": steps,+ "error": "Fusion did not come up within 120s of launch.",+ "_hint": "Check fusion_readiness; the box may be mid-update. Retry fusion_signin.",+ "statusVerb": "fusion_readiness"}+ _lt.sleep(5) if not rd.get("needsSignin"): return {"success": True, "stage": "already", "done": True, "steps": steps, "narrate": "Fusion is already signed in.",@@ -5208,6 +5297,9 @@ def _orchestrate_signin(args: dict) -> dict: steps.append("Fusion already opened the sign-in in %s (profile dir %r) - no re-open needed" % (target, target_dir)) wall0 = _detect_signin_wall()+ _email0 = args.get("email") or next((c.get("email") for c in choices if c.get("profile") == target and c.get("email")), None)+ if wall0.get("wall"):+ wall0 = _resolve_and_advance_wall(wall0, _email0) notified0 = bool(_notify_signin_wall(wall0["wall"]).get("delivered")) if wall0.get("wall") else False return {"success": True, "stage": "already_right_profile", "done": False, "steps": steps, "signinProfile": target, "signinProfileDir": target_dir,@@ -5319,6 +5411,9 @@ def _orchestrate_signin(args: dict) -> dict: # Did the reopened sign-in immediately hit a HUMAN WALL (2FA, credentials, protocol dialog)? # If so, toast the user AND tell the AI how to clear it for them. wall = _detect_signin_wall(ignore_hwnds=pre_hwnds) if opened else {}+ _email1 = args.get("email") or next((c.get("email") for c in choices if c.get("profile") == target and c.get("email")), None)+ if wall.get("wall"):+ wall = _resolve_and_advance_wall(wall, _email1) wall_kind = wall.get("wall") notified = False if wall_kind:@@ -5711,6 +5806,20 @@ def _orchestrate_demo_tour(args: dict) -> dict: except Exception: pass if not ready:+ # Not ready usually means the SIGN-IN wall on a fresh box. Drive it as far+ # as code can (email typed, page advanced), toast the human at the exact+ # remaining step, and fail the tour with instructions instead of a shrug.+ rd = _handle_fusion_readiness({}, {})+ if rd.get("needsSignin"):+ state.update(step="signin", stepLabel="Signing in (one-time, driving it for you)")+ si = _orchestrate_signin({"auto": True})+ wallname = si.get("signinWall") or "credentials"+ state.setdefault("warnings", []).append(+ "sign-in required: drove to wall %r; user toasted; %s" % (wallname, str(si.get("stage"))))+ raise RuntimeError(+ "One-time Autodesk sign-in needed on this machine. I drove it as far "+ "as possible and toasted the user at the %r step - after they finish, "+ "re-run the tour and it completes." % wallname) raise RuntimeError("Fusion launched but its add-in never became ready (150s)") elif key == "stage": lbr = _demo_tour_sample_lbr()
skills/fusion-autodesk-signin/SKILL.md+27@@ -110,3 +110,30 @@ time (learned the hard way here). Tell the user up front: "I need to install ABE browser reliably." Install: `bridge_install {manifestUrl: .../adom-browser-extension/files/native-browser-bridge-manifest.json}`, then the `installing-the-extension` flow, then `nbrowser_readiness` until `state:"ready"`. Coordinate- driving is the fallback of last resort, never the default.+++## AUTOMATED NOW: the verb self-drives this whole playbook (v1.9.32)++Everything above is what `fusion_signin` DOES ITSELF as of 1.9.32 - the manual playbook+remains as the fallback and the explanation. The contract for a calling AI:++1. Call `fusion_signin {}` (add `email` to override; the target profile's email is used+ otherwise). It SELF-STARTS Fusion if needed, clicks Sign In for the user, captures+ the OAuth URL, re-opens it in the correct profile, TYPES THE EMAIL and advances the+ page (background UIA, no focus steal), and classifies the remaining wall.+2. Walls you can get back, in `signinWall`:+ - `password` - the ONE human secret step. The user was ALREADY TOASTED with "just+ your password". Do nothing except poll `fusion_readiness`.+ - `twofactor_email_code` - fetch the code from Gmail (adom-google) and call+ `fusion_signin_2fa {code}` yourself; only fall back to the human if you cannot.+ - `protocol_handoff` - the browser asks to open Autodesk Identity Manager; the user+ was toasted to click Open.+3. NEVER re-toast, never ask the user for secrets, never re-drive an advanced page -+ the response `_hint` tells you exactly what remains.+4. The demo tour integrates this: on a signed-out box it drives sign-in to the wall,+ toasts, and fails with instructions - rerun the tour after the human's one step.++The philosophy (John, 2026-08-16): the code automates EVERYTHING automatable and+notifies the human at exactly the moment they are needed - they let this run in the+background, so the toast IS the interface. Hints teach the AI; the AI never improvises+clicks on auth pages.