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
fusion_signin: fix Fusion firing OAuth at the OS-default browser (wrong Autodesk account). Read the full authorize URL from the wrong browser's History DB and re-open it in the correct profile (idmgr/callback + autodesk:// handoff means any profile completes to the running Fusion). Remembers the profile; demo delegates to it. + fusion-multiprofile-signin skill
6 files changed
+292−7
BRIDGE_VERSION+1−1@@ -1 +1 @@-1.6.91+1.6.92
bridge.json+3−2@@ -2,7 +2,7 @@ "manifest_version": 1, "name": "fusion360", "displayName": "Autodesk Fusion 360",- "version": "1.6.91",+ "version": "1.6.92", "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/adom-desktop-fusion-bridge", "author": "Adom Inc.",@@ -173,7 +173,8 @@ "install_fusion": 90, "set_preference": 40, "get_preferences": 25,- "demo": 300+ "demo": 300,+ "signin": 150 }, "prefixes": [ {
describe.py+4@@ -37,6 +37,10 @@ _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. See the 'fusion-multiprofile-signin' skill.",+ {"profile": "optional str (chrome:<email>/edge:<email> - the profile your Autodesk account is in; remembered)"},+ {"stage": "str", "done": "bool", "signinWhere": "str", "signinProfile": "str", "signinProfileReason": "str", "signinWrongBrowser": "str", "signinAuthUrl": "str", "narrate": "str"},+ 120, "fusion_signin", True, {}), ("fusion_demo", "FIRST-TIME-USER DEMO in one verb - the thing to run when someone new wants to SEE what this bridge does (HD's installer calls this). Takes them from nothing to a real board: launches Fusion, FINISHES the Autodesk sign-in - it OPENS the sign-in in the user's NATIVE browser IN THE BACKGROUND (never interrupting their work) and returns signinWhere/signinProfile/signinSession so you can tell them exactly where it is waiting, then offer three paths: they finish it, you foreground it, or (with their OK) you drive it. Native browser via ABE/nbrowser_* because that profile is already signed into Autodesk - never pup, which is anonymous, sets up APS cloud search (or explains it if declined) and runs a LIVE sample search so they see 2-second results, then opens an electronics PROJECT and walks SCHEMATIC -> 2D BOARD -> 3D BOARD, screenshotting each view to show them. STAGED + RESUMABLE: every call advances as far as it safely can and returns stage/done/narrate/screenshots plus a _hint with the exact next action; keep calling it until done:true. NEVER leave a first-time user parked on the sign-in screen - that is the failure this verb exists to prevent. See the 'fusion-demo' skill.", {"query": "optional str (design to demo; else picked from APS search)", "stage": "optional str ('skip_aps' to proceed without cloud search)"}, {"stage": "str", "done": "bool", "narrate": "str", "screenshots": "[{label,path}]", "steps": "[str]", "apsSampleSearch": "[{name,project}]"},
page.json+4−4@@ -1,6 +1,6 @@ { "slug": "adom-desktop-fusion-bridge",- "version": "1.6.91",+ "version": "1.6.92", "type": "app", "description": "Installs this bridge's skills into your container so your AI knows how to drive the bridge. The bridge runtime itself is loaded by Adom Desktop from the release zip.", "dependencies": {@@ -52,7 +52,8 @@ "skills/fusion-onboarding/**", "skills/fusion-molecule-export/**", "skills/pcb-stackup/**",- "skills/fusion-demo/**"+ "skills/fusion-demo/**",+ "skills/fusion-multiprofile-signin/**" ], "discovery_triggers": [ "drive fusion 360",@@ -73,6 +74,5 @@ "set up the fusion bridge" ], "discovery_pitch": "Use the Adom Desktop Fusion 360 bridge when the user wants to DRIVE Autodesk Fusion 360 from the cloud: open/search their team's Fusion cloud designs, lay out a PCB or do electronics, export STEP/IGES/STL/Gerbers/BOM, or build a component library with real rendering 3D. It runs Fusion on the user's own machine via Adom Desktop. NOT for web/browser/login/form-filling (that's the native-browser extension) and NOT for KiCad (that's the kicad bridge).",- "brief": "Drive Autodesk Fusion 360 from the cloud via Adom Desktop: component libraries, IPC package generation, board layout, exports (STEP/Gerbers/BOM/CPL), fast APS cloud search, and parametric modeling.",- "visibility": "public"+ "brief": "Drive Autodesk Fusion 360 from the cloud via Adom Desktop: component libraries, IPC package generation, board layout, exports (STEP/Gerbers/BOM/CPL), fast APS cloud search, and parametric modeling." }
server.py+204@@ -3646,6 +3646,198 @@ def _ad_call(verb: str, args: dict, timeout: int = 60) -> dict: return {"_adCallError": "; ".join(why)} +def _fusion_signin_cfg_path():+ import pathlib+ d = pathlib.Path(os.path.expanduser("~")) / ".adom" / "fusion-signin"+ d.mkdir(parents=True, exist_ok=True)+ return d / "profile.json"+++def _remembered_signin_profile():+ try:+ import json as _j+ p = _fusion_signin_cfg_path()+ if p.exists():+ return (_j.loads(p.read_text()) or {}).get("profile") or None+ except Exception:+ pass+ return None+++def _remember_signin_profile(profile: str):+ try:+ import json as _j+ _fusion_signin_cfg_path().write_text(_j.dumps({"profile": profile}))+ except Exception:+ pass+++def _chrome_authorize_urls(max_age_min: int = 12):+ """Scan Chrome + Edge profile History DBs for RECENT Autodesk-desktop OAuth authorize+ URLs (the Fusion Identity SDK ones - marked by `idsdk` / redirect to idmgr/callback).++ This is how we fix Fusion opening the WRONG browser profile: Fusion fires its OAuth at+ the OS-default browser, but the FULL authorize URL (client_id, PKCE challenge, state,+ request_id) lands in that browser's History. We read it back and re-open it in the+ profile the user actually authenticates Autodesk in. The URL is NOT tied to a browser+ (its redirect is accounts.autodesk.com/idmgr/callback + an autodesk:// protocol handoff+ matched by request_id), so completing it in ANY profile hands the token to the running+ Fusion. Verified live (John, 2026-07-21).++ Returns a list of {url, sourceProfileDir, browser, ageSec} newest first.+ """+ import glob, sqlite3, shutil, tempfile, time as _t+ la = os.environ.get("LOCALAPPDATA", "")+ roots = []+ if la:+ roots.append(("chrome", os.path.join(la, "Google", "Chrome", "User Data")))+ roots.append(("edge", os.path.join(la, "Microsoft", "Edge", "User Data")))+ now = _t.time()+ found = []+ for browser, root in roots:+ if not os.path.isdir(root):+ continue+ for hist in glob.glob(os.path.join(root, "*", "History")):+ prof_dir = os.path.basename(os.path.dirname(hist))+ tmp = os.path.join(tempfile.gettempdir(), "adom_hist_%s_%s.db" % (browser, prof_dir))+ try:+ shutil.copy2(hist, tmp) # copy first - Chrome keeps History locked+ con = sqlite3.connect(tmp)+ rows = con.execute(+ "SELECT url, last_visit_time FROM urls "+ "WHERE url LIKE '%developer.api.autodesk.com/authentication/v2/authorize%' "+ "OR url LIKE '%idp.auth.autodesk.com/as/authorize%' "+ "ORDER BY last_visit_time DESC LIMIT 6").fetchall()+ con.close()+ except Exception:+ continue+ for url, cts in rows:+ if "idsdk" not in url and "idmgr" not in url:+ continue # only the DESKTOP-app (Fusion) flow, not a web app+ epoch = (cts / 1_000_000) - 11644473600+ age = now - epoch+ if age <= max_age_min * 60:+ found.append({"url": url, "sourceProfileDir": prof_dir,+ "browser": browser, "ageSec": int(age)})+ found.sort(key=lambda x: x["ageSec"])+ return found+++def _orchestrate_signin(args: dict) -> dict:+ """Sign Fusion in through the CORRECT browser profile - the fix for Fusion firing its+ OAuth at the OS-default browser (often the wrong Autodesk account). See the+ fusion-multiprofile-signin skill.++ Staged: pick the target profile (remembered / arg / probed / ask) -> ensure Fusion has+ emitted its OAuth URL (click Sign In if not) -> read that URL from the wrong browser's+ History -> re-open it in the TARGET profile in the BACKGROUND -> report where it waits.+ """+ steps = []+ target = args.get("profile") # explicit override, e.g. "chrome:[email protected]"+ if target:+ _remember_signin_profile(target)+ if not target:+ target = _remembered_signin_profile()++ 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"}+ if not rd.get("needsSignin"):+ return {"success": True, "stage": "already", "done": True, "steps": steps,+ "narrate": "Fusion is already signed in.",+ "_hint": "Already signed in (ready:%s). Nothing to do." % rd.get("ready"),+ "statusVerb": "fusion_signin"}++ # profiles known to ABE (for target selection + naming)+ profs = _ad_call("nbrowser_profiles", {}, timeout=40)+ pdata = profs.get("data", profs) if isinstance(profs, dict) else {}+ choices = [p for p in (pdata.get("profiles") or []) if isinstance(p, dict) and p.get("profile")]++ # read Fusion's authorize URL from browser history (it fires the OS-default browser)+ urls = _chrome_authorize_urls()+ if not urls:+ return {"success": True, "stage": "click_signin", "done": False, "steps": steps,+ "_hint": ("No recent Fusion OAuth URL in any browser's history yet - Fusion has not "+ "opened the sign-in browser. CLICK Fusion's 'Sign In' button (it is a "+ "webview with no UIA node, so image-space desktop_click on the button - "+ "screenshot the Fusion window for a shotId first), wait ~4s, then call "+ "fusion_signin again. This verb then grabs the URL Fusion opened in the "+ "WRONG default browser and re-opens it in the RIGHT profile."),+ "statusVerb": "fusion_signin"}+ auth = urls[0]+ steps.append("captured Fusion OAuth URL from %s profile '%s' (%ss old)"+ % (auth["browser"], auth["sourceProfileDir"], auth["ageSec"]))++ # choose the TARGET profile if not already fixed+ reason = ""+ if not target:+ # probe each live profile for a real Autodesk session; prefer signed-in, then work over consumer+ for c in choices:+ if not c.get("live"):+ continue+ ls = _ad_call("nbrowser_login_state",+ {"profile": c["profile"], "url": "https://accounts.autodesk.com/"}, timeout=40)+ lsd = ls.get("data", ls) if isinstance(ls, dict) else {}+ c["_ad"] = "signed-in" if lsd.get("loggedIn") else ("signed-out" if lsd.get("ok") is not None else "unprobed")+ c["_conf"] = lsd.get("confidence"); c["_cookies"] = lsd.get("cookieCount")+ signed = [c for c in choices if c.get("_ad") == "signed-in"]+ if signed:+ signed.sort(key=lambda c: ({"high": 3, "medium": 2, "low": 1}.get(c.get("_conf"), 0), c.get("_cookies") or 0), reverse=True)+ target = signed[0]["profile"]; reason = "it holds a live Autodesk session"+ else:+ _CONSUMER = ("gmail.com", "outlook.com", "hotmail.com", "yahoo.com", "icloud.com", "live.com")+ work = [c for c in choices if c.get("live") and c.get("email") and not any((c["email"] or "").lower().endswith("@" + d) for d in _CONSUMER)]+ if work:+ target = work[0]["profile"]; reason = "it is a work/corporate profile (no live Autodesk session detected - CONFIRM)"+ if target:+ _remember_signin_profile(target)+ reason = reason or "you told me to use it"++ if not target:+ return {"success": True, "stage": "ask_profile", "done": False, "steps": steps,+ "signinAuthUrl": auth["url"], "profileChoices": choices,+ "_hint": ("Captured Fusion's OAuth URL but cannot tell which profile is your Autodesk "+ "account. ASK the user which profile their Autodesk login belongs to, then "+ "call fusion_signin {profile:'chrome:<their-account>'} - I will remember it. "+ "profileChoices lists every profile."),+ "statusVerb": "fusion_signin"}++ tdesc = next((_describe_profile(c) for c in choices if c.get("profile") == target), target)+ r = _ad_call("nbrowser_open_window",+ {"sessionId": "fusion-signin", "url": auth["url"], "background": True,+ "profile": target, "thread": "fusion-signin",+ "purpose": "Fusion Autodesk sign-in (correct profile)"}, timeout=60)+ opened = bool((r.get("data", r) if isinstance(r, dict) else {}).get("sessionId") or r.get("ok") or r.get("success"))+ steps.append(("re-opened it in %s" % tdesc) if opened else ("could not open %s: %s" % (tdesc, r.get("_adCallError"))))++ return {"success": True, "stage": "opened" if opened else "open_failed",+ "done": False, "steps": steps, "signinWhere": tdesc, "signinProfile": target,+ "signinProfileReason": reason, "signinSession": "fusion-signin" if opened else None,+ "signinWrongBrowser": "%s / %s" % (auth["browser"], auth["sourceProfileDir"]),+ "signinAuthUrl": auth["url"],+ "narrate": (("Fusion opened its sign-in in the WRONG browser (%s), so I moved the real "+ "Autodesk sign-in URL into %s - I picked that because %s. Finish it there, "+ "or I can drive it, and Fusion will pick up the login automatically."+ % (auth["sourceProfileDir"], tdesc, reason)) if opened else+ "I captured Fusion's sign-in URL but could not open the target profile."),+ "_hint": (("The REAL Fusion OAuth URL is now open in %s (session 'fusion-signin'). It "+ "completes back to the running Fusion via the idmgr/callback + autodesk:// "+ "protocol handoff (request_id match), so the browser profile no longer has to "+ "be the OS default - THIS is the fix for Fusion signing into the wrong "+ "account. TELL the user the exact profile (never 'your browser'), say WHY it "+ "was chosen (signinProfileReason), and offer: they finish it, you foreground "+ "it, or (with their OK) you drive 'Continue with Google/Apple/Microsoft'. "+ "NEVER type their password/2FA. Then poll fusion_readiness until ready:true. "+ "The wrong-browser tab (%s) can be closed. If the wrong ACCOUNT completes "+ "anyway, sign Fusion out and re-run. Skill: fusion-multiprofile-signin."+ % (tdesc, auth["sourceProfileDir"])) if opened else+ "Open failed: %s. Retry, or open auth url yourself with nbrowser_open_window "+ "{profile:'%s', url:<signinAuthUrl>}." % (r.get("_adCallError"), target)),+ "statusVerb": "fusion_signin"}++ def _orchestrate_demo(args: dict) -> dict: """FIRST-TIME-USER DEMO: take a brand-new user from nothing to "wow" in one verb. @@ -3708,6 +3900,16 @@ def _orchestrate_demo(args: dict) -> dict: # ── 2. SIGN-IN: finish it, do not sit on it ───────────────────────────────── if rd.get("needsSignin"):+ # Delegate to the multi-profile sign-in fix (reads Fusion's OAuth URL out of the WRONG+ # default browser and re-opens it in the RIGHT profile). Returns its own rich stage.+ si = _orchestrate_signin({"profile": args.get("signinProfile")})+ si["demo"] = True+ if si.get("stage") in ("opened", "click_signin", "ask_profile", "open_failed"):+ si.setdefault("done", False)+ si["_hint"] = (si.get("_hint", "") + " (This is the fusion_demo sign-in stage - after "+ "the user is signed in and fusion_readiness is ready:true, call fusion_demo "+ "again to continue the tour: APS -> project -> schematic -> 2D -> 3D.)")+ return si # DO IT, don't just describe it: open the Autodesk sign-in in the user's OWN browser, # in the BACKGROUND so we never yank them out of what they're doing. Then tell the AI # exactly WHERE it is waiting and offer the three ways forward.@@ -4140,6 +4342,8 @@ def dispatch_command(command: str, args: dict) -> dict: return _orchestrate_fetch_optimized_glb(args) if command == "demo": return _orchestrate_demo(args)+ if command == "signin":+ return _orchestrate_signin(args) if command == "board_stackup": return _orchestrate_board_stackup(args)
skills/fusion-multiprofile-signin/SKILL.mdadded+76@@ -0,0 +1,76 @@+---+name: fusion-multiprofile-signin+description: >+ Fix Fusion 360 signing into the WRONG Autodesk account because it fires its OAuth at the OS-DEFAULT+ browser. Fusion opens sign-in in whatever browser is default (often a personal Chrome profile), but+ a user's Autodesk seat usually lives in a DIFFERENT profile (their work Google/SSO). The bridge's+ fusion_signin verb reads the full authorize URL Fusion wrote into that wrong browser's history and+ re-opens it in the RIGHT profile, where it completes back to the running Fusion. Trigger words -+ fusion wrong browser, fusion wrong account, fusion signs into wrong profile, fusion opens personal+ chrome, autodesk wrong profile, multiple chrome profiles autodesk, fusion_signin, fusion sign in+ wrong google account, fusion default browser oauth, authenticate fusion multiple profiles.+---++# Signing Fusion into the right browser profile (fixing Fusion's dumb OAuth)++## The problem (Fusion's fault, we work around it)++Fusion's desktop sign-in fires an OAuth URL at the **OS-default browser**. If your Autodesk account+lives in a *different* Chrome/Edge profile - personal Chrome is default but your seat is+`[email protected]` in your work profile - Fusion signs you into the wrong account or stalls on the wrong+Google session. Autodesk should let you pick the profile; they don't. So we do.++Verified live (John, 2026-07-21): Fusion opened his personal Chrome (`[email protected]`) when his+Autodesk login is `[email protected]` in his work profile.++## The fix: `fusion_signin`++```bash+adom-desktop fusion_signin '{"profile":"chrome:[email protected]"}' # fix + REMEMBER your profile+adom-desktop fusion_signin '{}' # auto: probe/remember/ask+```++What it does:++1. **Reads the full authorize URL out of the wrong browser's History.** Fusion writes a complete+ `developer.api.autodesk.com/.../authorize?client_id=...&code_challenge=...&state=...&request_id=idsdk-...`+ into whatever browser it opened. The address bar rewrites within a second (the Autodesk SPA+ cleans it), but Chrome's/Edge's **History DB keeps the full URL** - so the bridge copies the+ locked History file and reads the newest Fusion (`idsdk`/`idmgr`) authorize entry. Racing the+ address bar over the relay does NOT work; the History read is the reliable capture.+2. **Re-opens that exact URL in the RIGHT profile, backgrounded** (ABE / `nbrowser_open_window`), so+ it never interrupts what the user is doing.+3. It completes back to the **running Fusion** on its own. The redirect is+ `accounts.autodesk.com/idmgr/callback` + an `autodesk://` protocol handoff matched by+ `request_id`, **not** a browser-specific localhost port - so the profile no longer has to be the+ OS default. Any profile that finishes the login hands the token to Fusion.++## Picking the right profile++- **Best:** the user tells you once (`{profile:"chrome:[email protected]"}`); the bridge **remembers** it+ (`~/.adom/fusion-signin/profile.json`) for every future sign-in.+- **Auto:** the verb probes each live profile with `nbrowser_login_state` against+ accounts.autodesk.com and prefers the one holding a real Autodesk session; failing that, a+ work/corporate identity over a consumer mailbox - and says it is guessing.+- **Unsure:** it returns `stage:"ask_profile"` - ASK which account their Autodesk login is, do not+ assume. Name profiles by identity (`Chrome - you ([email protected])`), never "your browser".++## The staged loop++- `stage:"click_signin"` - Fusion has not opened the browser yet. Click Fusion's **Sign In** (it is a+ webview with no UIA node, so an image-space `desktop_click` on the button; screenshot the Fusion+ window first for a shotId), wait ~4s, call `fusion_signin` again.+- `stage:"opened"` - the URL is now in the right profile (`signinWhere`). Tell the user where, why+ (`signinProfileReason`), and offer: they finish it, you foreground it, or (with their OK) you drive+ "Continue with Google/Apple/Microsoft". **Never type their password/2FA** - escalate a real secret+ with `fusion_notify_owner`. Poll `fusion_readiness` until `ready:true`.+- `stage:"ask_profile"` - ask which profile, then re-call with it.++## Gotchas++- Read History AFTER Fusion has opened the browser, and take the NEWEST authorize entry - a stale one+ from a previous attempt carries a dead `request_id` and Fusion (waiting on a new one) never gets it.+- If the wrong account completes in the default browser before you move it, sign Fusion out and re-run.+- Chrome keeps History locked; always COPY the file before opening it (the verb does this).+- This is also the sign-in stage `fusion_demo` uses, so a first-time demo never lands in the wrong+ Autodesk account.