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
aps.py+143−11@@ -104,8 +104,10 @@ def _aps_auth001_recovery_hint(client_id: str) -> str: return ( "⚠ APS AUTH-001 - the APS app (Client ID " + (client_id[:8] + "...") + ") LOST its Data Management " "API product, so authorize fails BEFORE any login. This is fixable; here is the exact recovery "- "(drive with ABE/nbrowser - if not installed, install adom-browser-extension first, it is the "- "ONLY reliable way to drive the portal): "+ "(this one genuinely needs ABE/nbrowser: recovery CLICKS and EVALs inside the portal, which the "+ "extension-free nbrowser_open_os_window cannot do - it only opens a URL. If the extension is not "+ "installed, install adom-browser-extension first; ABE can do it automatically, roughly a 5-minute "+ "AI-driven process. Alternatively walk the USER through steps 1-3 by hand): " "(1) nbrowser_open_window {sessionId, thread, purpose, profile:<the warm Edge/Chrome profile>, " "url:'https://aps.autodesk.com/myapps/'} and open the app whose Client ID is this one. " "(2) On the app's App-settings page, API Access section: confirm 'Data Management API' is checked, "@@ -227,6 +229,106 @@ _signin_lock = threading.Lock() _signin_state = None # {"verifier","state","server","thread","done","error","startedAt"} +# ── Extension-free browser opening (issue #12) ───────────────────────────────+# ABE gained OS-level, EXTENSION-FREE verbs (nbrowser_open_os_window / nbrowser_os_windows):+# they open a URL in a REAL browser PROFILE without the Adom extension being installed in that+# Chrome/Edge. That is exactly the OAuth-consent case, so APS sign-in no longer dead-ends on a+# machine with no extension (Caleb, issue #12). The extension is still WORTH having - with it we+# can drive/see/screenshot the window instead of asking the user to click - so we upsell it.+_ABE_UPSELL = ("OPTIONAL, offer it: installing the Adom browser extension lets me DRIVE this "+ "sign-in instead of asking the user to click (fill, read the result, screenshot, "+ "clean up). ABE can install it for them automatically - it is just slow, roughly a "+ "5-minute AI-driven process. See the 'installing-the-extension' flow. Everything "+ "here works WITHOUT it; the extension only buys automation.")+++def _ad(verb: str, args: dict, timeout: int = 60) -> dict:+ """Call an AD verb from aps.py. Lazy-imports server (which imports us) so there is no+ circular import at module load. Never raises."""+ try:+ import server as _s+ return _s._ad_call(verb, args, timeout=timeout) or {}+ except Exception as e:+ return {"_adCallError": str(e)}+++def _abe_state() -> dict:+ """Probe ABE readiness so the response can say WHY a path was chosen, and whether the+ (optional) extension is present. Degrades to {'known': False} on any failure."""+ r = _ad("nbrowser_readiness", {}, timeout=30)+ if not isinstance(r, dict) or r.get("_adCallError") or "ready" not in r:+ return {"known": False,+ "note": "could not probe ABE (verb missing or CLI too old); the extension-free "+ "path does not need it"}+ return {"known": True,+ "ready": r.get("ready"),+ "extensionConnected": r.get("extensionConnected"),+ "profiles": r.get("profiles") or [],+ "chromeInstalled": r.get("chromeInstalled"),+ "extensionStale": r.get("extensionStale"),+ "staleProfiles": r.get("staleProfiles") or []}+++def _open_os_window_extension_free(url: str, profile: str = None) -> dict:+ """Open `url` in the user's REAL browser profile with NO extension required.++ Returns {launched: bool, where: str, profile: str, error: str}. `launched` is intentionally+ generous: nbrowser_open_os_window returns ok:false when it cannot resolve the new window's+ hwnd within ~8s, but the browser usually DID open (the tab often groups into an existing+ window). For a consent flow, "the page is in front of the user" is what matters, so a fired+ launch counts.+ """+ if not profile:+ # reuse the Autodesk profile the user already picked for Fusion's own sign-in+ try:+ import server as _s+ profile = _s._remembered_signin_profile()+ except Exception:+ profile = None+ a = {"sessionId": "aps-signin-%d" % int(time.time()),+ "thread": "fusion-aps-signin",+ "purpose": "Autodesk APS consent (extension-free)",+ "url": url, "background": True}+ if profile:+ a["profile"] = profile+ r = _ad("nbrowser_open_os_window", a, timeout=60)+ if not isinstance(r, dict) or r.get("_adCallError"):+ return {"launched": False, "profile": profile,+ "error": (r or {}).get("_adCallError") or "nbrowser_open_os_window unavailable"}+ launched = bool(r.get("ok") or r.get("hwnd") or "Launch fired" in str(r.get("_hint", "")))+ return {"launched": launched,+ "where": r.get("displayName") or r.get("profile") or profile,+ "profile": r.get("profile") or profile,+ "hwnd": r.get("hwnd"),+ "extensionInstalled": r.get("extensionInstalled"),+ "error": None if launched else (str(r.get("error") or r.get("_hint") or "")[:200])}+++def _signin_setup_chain(opened: bool, abe: dict) -> list:+ """A chain the agent can follow MECHANICALLY (Caleb's ask), instead of a bare command that+ may not exist on the box."""+ if opened:+ return [{"step": "user_approves",+ "do": "Tell the user the exact profile it opened in and ask them to click Approve."},+ {"step": "poll", "verb": "fusion_aps_status",+ "until": "signedIn:true"}]+ chain = [{"step": "open_consent", "verb": "nbrowser_open_os_window",+ "args": {"sessionId": "<task id>", "thread": "<your thread>",+ "purpose": "Autodesk APS consent", "profile": "chrome:<their-autodesk-email>",+ "url": "<authUrl from this response>"},+ "note": "EXTENSION-FREE - works with no Adom extension installed. If this verb is "+ "unknown, the adom-desktop CLI on this box is too old: update it (or have "+ "the user paste authUrl into the right profile by hand)."},+ {"step": "user_approves",+ "do": "Ask the user to approve in that profile; without the extension we cannot click for them."},+ {"step": "poll", "verb": "fusion_aps_status", "until": "signedIn:true"}]+ if abe.get("known") and not abe.get("extensionConnected"):+ chain.append({"step": "optional_extension", "flow": "installing-the-extension",+ "why": "lets the assistant DRIVE the sign-in instead of asking for clicks",+ "cost": "~5 minutes, ABE automates it"})+ return chain++ # ── Config + token storage ─────────────────────────────────────────────────── def _read_json(path: Path) -> dict: try:@@ -533,6 +635,8 @@ def start_signin(args: dict | None = None) -> dict: pref = passed or load_signin_browser() opened_via = None opened_pref = None+ ext_free = None # result of the EXTENSION-FREE open, if we take that path+ abe = _abe_state() # probed once, reported either way (issue #12) if pref and _launch_url_in_browser(pref, auth_url): opened_via = "remembered_browser" if not passed else "specified_browser" opened_pref = {k: pref.get(k) for k in ("name", "exe", "profileDir")}@@ -551,7 +655,17 @@ def start_signin(args: dict | None = None) -> dict: except Exception: opened_via = None else:- opened_via = "none_use_extension"+ # EXTENSION-FREE open (issue #12, Caleb): we used to stop here and hand back a hint+ # telling the AI to run nbrowser_open_window - which REQUIRES the ABE extension. On a+ # box without it (or on a CLI too old to know the verb) that hint was un-followable and+ # the whole sign-in dead-ended. ABE now exposes nbrowser_open_os_window, which opens a+ # URL in a REAL browser PROFILE with NO extension installed - exactly the OAuth-consent+ # case. Try that before giving up.+ ext_free = _open_os_window_extension_free(auth_url, args.get("profile"))+ if ext_free.get("launched"):+ opened_via = "os_window_extension_free"+ else:+ opened_via = "none_use_extension" if opened_via in ("remembered_browser", "specified_browser"): _hint = (f"Opened the Autodesk consent in the REMEMBERED browser ({opened_pref.get('name') or 'cached'}"@@ -562,25 +676,43 @@ def start_signin(args: dict | None = None) -> dict: _hint = ("Opened in the OS DEFAULT browser (fallback). On a multi-browser machine this may be the " "wrong profile; prefer driving authUrl in the user's real browser via nbrowser_open_window " "(new window), then call fusion_aps_set_browser to remember it. Poll fusion_aps_status.")+ elif opened_via == "os_window_extension_free":+ _hint = ("Opened the Autodesk consent in %s using the EXTENSION-FREE path "+ "(nbrowser_open_os_window) - no Adom browser extension required. TELL THE USER which "+ "profile it opened in (never just 'your browser') and that THEY need to click Approve, "+ "because without the extension I cannot see or drive that window. A listener on port "+ "%s captures the token once they approve. Poll fusion_aps_status until signedIn:true. "+ "%s" % (ext_free.get("where") or "the user's browser profile", bound_port,+ _ABE_UPSELL)) else:- _hint = ("NO browser opened on purpose. This machine has no REMEMBERED Autodesk browser yet, and a "- "multi-browser box's OS default is usually the wrong one. OPEN authUrl in the user's REAL "- "browser via a NEW window (adom-desktop nbrowser_open_window {url:authUrl}) so its warm "- "Autodesk/SSO session is used, then call fusion_aps_set_browser {name,exe,profileDir} to "- "REMEMBER it for next time. A listener on port "- f"{bound_port} captures the token once you approve. Poll fusion_aps_status until signedIn:true.")-+ _hint = ("Could not open a browser for you. No REMEMBERED Autodesk browser, and the EXTENSION-FREE "+ "open (nbrowser_open_os_window) did not fire%s. DO THIS: run the setupChain in this "+ "response in order. Simplest manual fallback - have the USER paste authUrl into the "+ "browser profile their Autodesk account lives in. A listener on port "+ "%s captures the token once they approve; poll fusion_aps_status until signedIn:true. %s"+ % ((": " + str(ext_free.get("error"))[:120]) if ext_free and ext_free.get("error") else "",+ bound_port, _ABE_UPSELL))++ ok_open = bool(opened_via and opened_via != "none_use_extension") return { "success": True, "output": ("Autodesk sign-in started. " + ( "Approve access in the opened browser, then call fusion_aps_status."- if opened_via and opened_via != "none_use_extension"+ if ok_open else "Open authUrl in the user's real browser (new window) to approve.")), "authUrl": auth_url, "redirectUri": _active_redirect_uri, "openedVia": opened_via, "openedBrowser": opened_pref,+ # Extension-free open no longer NEEDS the extension - keep the old flag truthful for+ # existing callers, but only assert it when we genuinely could not open anything. "needsExtensionOpen": opened_via == "none_use_extension",+ "extensionFree": ext_free,+ # Caleb's asks (issue #12): a structured code + a chain the agent can follow mechanically,+ # instead of a raw command that may not exist on this box.+ "errorCode": None if ok_open else "browser_open_failed",+ "abe": abe,+ "setupChain": _signin_setup_chain(ok_open, abe), "_hint": _hint, }