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
demo signin: bridge now OPENS the Autodesk sign-in in the user's native browser IN THE BACKGROUND (via ad_call -> nbrowser_*), reports where it is waiting, and offers all three paths (user finishes / AI foregrounds / AI drives with permission)
6 files changed
+112−27
BRIDGE_VERSION+1−1@@ -1 +1 @@-1.6.85+1.6.86
bridge.json+1−1@@ -2,7 +2,7 @@ "manifest_version": 1, "name": "fusion360", "displayName": "Autodesk Fusion 360",- "version": "1.6.85",+ "version": "1.6.86", "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.",
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_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 (driven in the user's NATIVE browser via ABE/nbrowser_*, which 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.",+ ("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}]"}, 240, "fusion_demo", True, {}),
page.json+2−3@@ -1,6 +1,6 @@ { "slug": "adom-desktop-fusion-bridge",- "version": "1.6.85",+ "version": "1.6.86", "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": {@@ -73,6 +73,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+87−20@@ -3578,6 +3578,40 @@ def _orchestrate_fetch_optimized_glb(args: dict) -> dict: "message": "Job %s still %s; re-call fusion_fetch_optimized_glb to finish." % (job_id, res.get("status") or "processing")} +def _ad_call(verb: str, args: dict, timeout: int = 60) -> dict:+ """Call another AD verb (nbrowser_*, desktop_*) from inside this bridge.++ Prefers the in-process ad_client; falls back to the adom-desktop CLI (which joins the+ relay itself) so this still works on an unattended VM where ad_client is down. Never+ raises - returns {} on failure so callers can degrade to instructing the AI instead."""+ try:+ if ad_client.available():+ r = ad_client.call(verb, args)+ if isinstance(r, dict):+ return r+ except Exception:+ pass+ try:+ import subprocess as _sp, json as _j+ exe = _find_adom_desktop_cli()+ if not exe:+ return {}+ p = _sp.run([exe, verb, _j.dumps(args)], capture_output=True, text=True, timeout=timeout)+ out = (p.stdout or "").strip()+ if out.startswith("{"):+ d = _j.loads(out)+ inner = d.get("output")+ if isinstance(inner, str) and inner.strip().startswith("{"):+ try:+ return _j.loads(inner)+ except Exception:+ return d+ return d+ except Exception:+ pass+ return {}++ def _orchestrate_demo(args: dict) -> dict: """FIRST-TIME-USER DEMO: take a brand-new user from nothing to "wow" in one verb. @@ -3640,26 +3674,59 @@ def _orchestrate_demo(args: dict) -> dict: # ── 2. SIGN-IN: finish it, do not sit on it ───────────────────────────────── if rd.get("needsSignin"):- return _out("signin",- "Fusion needs your Autodesk sign-in. I'll use your own browser - you're probably "- "already signed in there, so this should take one click.",- "FINISH THE SIGN-IN - do NOT leave the user staring at this screen. Use the NATIVE "- "browser (ABE), never pup: pup is anonymous so Autodesk would demand a full login, "- "while the user's real Chrome/Edge profile is typically ALREADY signed into Autodesk. "- "SEQUENCE: (1) nbrowser_readiness - if state!='ready', offer the one-time extension "- "install (installing-the-extension skill); ABE still exposes nb verbs meanwhile. "- "(2) Click Fusion's 'Sign In' button in the BACKGROUND via desktop_find_control + "- "desktop_ui_click (never a coordinate click, never steal focus). That fires the OAuth "- "URL at the DEFAULT browser. (3) If their default browser is the signed-in profile, it "- "completes itself - just poll fusion_readiness. If it lands logged-out, open the same "- "auth URL in the signed-in profile with nbrowser_open_window {profile:'chrome:<their "- "account>'} (nbrowser_profiles lists them) and click 'Allow'/'Open' there. (4) The "- "handoff back to Fusion is the 'Autodesk Identity Manager' overlay - tick 'Always allow' "- "+ 'Open'. (5) NEVER type their password or 2FA: if a secret is demanded, "- "fusion_notify_owner toasts their machine so THEY type it. Sign-in codes expire in ~2 "- "min - if it expires, click 'Sign In' again for a fresh one. Poll fusion_readiness until "- "ready:true, then call fusion_demo again. FULL PLAYBOOK: fusion-autodesk-signin skill.",- needsSignin=True)+ # 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.+ nb = _ad_call("nbrowser_readiness", {}, timeout=40)+ nb_data = nb.get("data", nb) if isinstance(nb, dict) else {}+ nb_state = nb_data.get("state")+ opened, where, profile_used = False, "", ""+ if nb_state == "ready":+ profs = _ad_call("nbrowser_profiles", {}, timeout=40)+ pdata = profs.get("data", profs) if isinstance(profs, dict) else {}+ plist = [p for p in (pdata.get("profiles") or []) if isinstance(p, str) and ":" in p]+ profile_used = plist[0] if plist else ""+ oa = {"sessionId": "fusion-signin", "url": "https://accounts.autodesk.com/",+ "background": True, "thread": "fusion-demo", "purpose": "Autodesk sign-in for Fusion"}+ if profile_used:+ oa["profile"] = profile_used+ r = _ad_call("nbrowser_open_window", oa, timeout=60)+ opened = bool((r.get("data", r) if isinstance(r, dict) else {}).get("sessionId") or r.get("ok") or r.get("success"))+ where = (profile_used.split(":", 1)[0].title() if profile_used else "your browser")+ if profile_used and "@" in profile_used:+ where += " (%s)" % profile_used.split(":", 1)[1]+ steps.append("opened Autodesk sign-in in %s (background)" % (where or "browser") if opened+ else "could not auto-open the browser sign-in")+ if opened:+ narrate = ("I've opened the Autodesk sign-in for you in %s - it's sitting in the "+ "background so it won't interrupt what you're doing. Finish it whenever you "+ "like, or say the word and I'll bring it up front, or I can just complete it "+ "for you." % where)+ else:+ narrate = ("Fusion needs your Autodesk sign-in. I'd like to use your own browser - you're "+ "probably already signed in there, so it should take one click.")+ return _out("signin", narrate,+ ("FINISH THE SIGN-IN - never leave a first-time user parked here. " ++ ("A background sign-in tab is ALREADY OPEN in %s (session 'fusion-signin'). " % where+ if opened else "Auto-open failed - open it yourself: nbrowser_open_window "+ "{sessionId:'fusion-signin', url:'https://accounts.autodesk.com/', background:true, "+ "profile:'<chrome:their-account>'}. ") ++ "TELL THE USER WHERE IT IS WAITING and OFFER ALL THREE: (a) they finish it themselves "+ "whenever they want, (b) you foreground that window for them "+ "(nbrowser_switch_window / browser window state), or (c) THEY LET YOU DRIVE IT - ask "+ "first (AskUserQuestion), then click through 'Continue with Google/Apple/Microsoft' "+ "using their warm session. Use the NATIVE browser (ABE), NEVER pup: pup is anonymous so "+ "Autodesk demands a full fresh login, while their real profile is usually already signed "+ "in. NEVER type their password or 2FA - if a secret is demanded, fusion_notify_owner "+ "toasts them to type it. Then click Fusion's own 'Sign In' (the webview exposes no UIA "+ "control, so use an image-space desktop_click on the button, or foreground + click) to "+ "fire the OAuth handoff; accept the 'Autodesk Identity Manager' overlay ('Always allow' "+ "+ 'Open'). Codes expire in ~2 min - if it expires, click Sign In again. Poll "+ "fusion_readiness until ready:true, then call fusion_demo again. Playbook: "+ "fusion-autodesk-signin."),+ needsSignin=True, signinOpened=opened, signinWhere=where,+ signinProfile=profile_used, signinSession="fusion-signin" if opened else None,+ nbState=nb_state) if not rd.get("ready"): return _out("waiting",
skills/fusion-demo/SKILL.md+20−1@@ -45,7 +45,26 @@ Loop: call it, do what `_hint` says, narrate, call it again. Do not improvise a ## Rule 1: FINISH the sign-in, in the user's NATIVE browser -When `stage:"signin"`:+`fusion_demo` does not just tell you to do this - at `stage:"signin"` it **already opened the+Autodesk sign-in in the user's own browser, in the BACKGROUND**, so their current work is never+interrupted. The response tells you where it landed:++| Field | Meaning |+|---|---|+| `signinOpened` | true if the background tab is up |+| `signinWhere` | human phrasing for WHERE, e.g. "Chrome ([email protected])" - say this out loud |+| `signinProfile` / `signinSession` | the ABE profile + sessionId (`fusion-signin`) to drive it |++**Always tell the user where it is waiting, then offer all three paths:**++1. **They finish it themselves**, whenever they like - it is sitting there, backgrounded.+2. **You bring it to the front** for them (foreground that nb window) if they would rather do it now.+3. **You drive it for them** - ASK first (`AskUserQuestion`), then click through+ "Continue with Google/Apple/Microsoft" on their warm session.++Never silently sit and wait, and never foreground it uninvited.++Then: - **Use ABE (`nbrowser_*`), never pup.** pup is an anonymous browser, so Autodesk demands a full login with password + 2FA. The user's real Chrome/Edge profile is **typically already signed into