← Commit history

add fusion_demo: one-verb first-time-user tour (finishes Autodesk sign-in via native browser, sets up APS + live sample search, walks project -> schematic -> 2D board -> 3D board with screenshots). Docs, hints, skill, README section

John Lauer ·976b94ec37 ·2mo ago ·parent 730c158
7 files changed +310−7
BRIDGE_VERSION+1−1
@@ -1 +1 @@-1.6.77+1.6.84
README.md+31
@@ -10,6 +10,37 @@ calling AI never has to guess. "generate an IPC SOIC-8 with LM358 etched on top and export the STEP"   - just ask Claude ``` +## The demo: one verb, a whole guided tour (`fusion_demo`)++New to this? Run **one verb** and watch:++```bash+adom-desktop fusion_demo '{}'+```++`fusion_demo` takes a first-time user from nothing to a real board. It launches Fusion, **finishes+the Autodesk sign-in**, turns on cloud search, and then walks the actual story of a PCB:++**electronics project -> schematic -> 2D board layout -> 3D board**, screenshotting each view.++It is **staged and resumable**: each call advances as far as it safely can and returns `stage`,+`done`, a `narrate` line written for a newcomer, the `screenshots` to show them, and a `_hint` with+the exact next action. Keep calling it until `done:true`.++Two things it deliberately does NOT do:++- **It never leaves you parked on the sign-in page.** It drives the sign-in through your **native+  browser** (via the Adom browser extension), because that profile is usually already signed into+  Autodesk - one click instead of a password + 2FA. An anonymous automation browser would force a+  full fresh login, so the demo never uses one.+- **It never just name-drops cloud search.** It sets **APS** up for you, then runs a live sample+  search so you watch your whole Autodesk hub return results in about two seconds.++If you decline the cloud-search setup, run `fusion_demo '{"stage":"skip_aps"}'` and the tour+continues without it. To demo a specific design: `fusion_demo '{"query":"BQ25792"}'`.++Playbook for the AI running it: the bundled **fusion-demo** skill.+ ## Component library manager - the Adom basic parts library  The bridge builds and opens full EAGLE-format component libraries in Fusion's library editor. Below:
bridge.json+1−1
@@ -2,7 +2,7 @@   "manifest_version": 1,   "name": "fusion360",   "displayName": "Autodesk Fusion 360",-  "version": "1.6.77",+  "version": "1.6.84",   "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+5
@@ -37,6 +37,11 @@ _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.",+     {"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, {}),+     # ── Cloud search & files (APS) ─────────────────────────────────────────     ("fusion_aps_status", "APS state: configured, signed in, token live, never-charge meter.",      {}, {"configured": "bool", "signedIn": "bool", "neverCharge": "bool"}, 20, None, False, {}),
page.json+4−4
@@ -1,6 +1,6 @@ {   "slug": "adom-desktop-fusion-bridge",-  "version": "1.6.83",+  "version": "1.6.84",   "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": {@@ -51,7 +51,8 @@     "skills/fusion-multipart-libraries/**",     "skills/fusion-onboarding/**",     "skills/fusion-molecule-export/**",-    "skills/pcb-stackup/**"+    "skills/pcb-stackup/**",+    "skills/fusion-demo/**"   ],   "discovery_triggers": [     "drive fusion 360",@@ -72,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." }\ No newline at end of file
server.py+174−1
@@ -1348,7 +1348,9 @@ def _handle_fusion_readiness(fusion_info: dict, args: dict) -> dict:                 "sign-in code EXPIRES in ~2 min; on a high-latency VM slow screenshot/click round-trips "                 "let it expire ('Sign-in request expired') and you loop. Minimize steps; if it expires, "                 "click Fusion 'Sign In' again for a FRESH code (the browser session stays warm). "-                "Then poll until ready:true.")+                "Then poll until ready:true. FIRST-TIME DEMO? Run fusion_demo instead - it drives this "+                "sign-in through the user's NATIVE browser (already signed into Autodesk) and then "+                "gives them a real guided tour (project -> schematic -> 2D board -> 3D board).")     elif ready and stale_info.get("addinStale"):         hint = stale_info["_staleHint"]     elif ready:@@ -3576,6 +3578,175 @@ 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 _orchestrate_demo(args: dict) -> dict:+    """FIRST-TIME-USER DEMO: take a brand-new user from nothing to "wow" in one verb.++    Written because HD's installer ran a "demo" that just opened Fusion and SAT on the+    sign-in page doing nothing (John, 2026-07-20). A demo must actually finish the sign-in,+    set up APS, and SHOW the user real work: an electronics PROJECT -> SCHEMATIC -> 2D BOARD+    -> 3D BOARD, plus a live APS cloud search.++    STAGED + RESUMABLE. Each call advances as far as it safely can and returns:+      stage      - what it just did / is waiting on+      done       - whether the demo finished+      narrate    - a first-time-user-friendly line the CALLER should say + toast+      screenshots- images to show the user for this stage+      _hint      - exactly what the AI must do next (including nbrowser_* for the browser leg)++    The bridge owns Fusion; it does NOT own the browser. So when the sign-in needs a browser+    we hand the caller precise `nbrowser_*` (ABE) instructions - the user's NATIVE browser is+    already signed into Autodesk, whereas pup is anonymous and would force a fresh login.+    """+    stage = (args.get("stage") or "auto").strip()+    demo_query = args.get("query") or ""          # optional: which design to demo+    steps: list = []+    shots: list = []++    def _shot(label):+        try:+            r = _handle_screenshot_fusion({}, {})+            if r.get("success") and r.get("localSafePath"):+                shots.append({"label": label, "path": r.get("localSafePath")})+        except Exception:+            pass++    def _out(stage_name, narrate, hint, done=False, **extra):+        return {"success": True, "demo": True, "stage": stage_name, "done": done,+                "steps": steps, "narrate": narrate, "screenshots": shots,+                "statusVerb": "fusion_demo", "_hint": hint, **extra}++    # ── 1. Fusion present + running ─────────────────────────────────────────────+    rd = _handle_fusion_readiness({}, {})+    if not rd.get("installed"):+        return _out("not_installed",+            "Fusion 360 isn't installed yet - I can install it for you (free trial is plenty for this demo).",+            "OFFER the install, then call fusion_install_fusion (streams the free trial, 10-30 min; "+            "poll fusion_readiness until installed:true), then call fusion_demo again. See the "+            "fusion-onboarding skill.")+    if not rd.get("running"):+        steps.append("launched Fusion")+        _handle_launch({}, {})+        rd = _handle_fusion_readiness({}, {})++    # ── 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)++    if not rd.get("ready"):+        return _out("waiting",+            "Fusion is starting up - one moment.",+            "Not ready yet (add-in still loading, or a licensing dialog). Poll fusion_readiness; it "+            "auto-resolves seat dialogs. When ready:true, call fusion_demo again. Reason: " ++            str(rd.get("_hint", ""))[:220])+    steps.append("Fusion ready")++    # ── 3. APS: set it up if we can, otherwise SELL it (never skip silently) ─────+    aps_ready = False+    try:+        ap = aps.handle_status({}).get("data", {})+        aps_ready = bool(ap.get("configured") and ap.get("signedIn") and ap.get("tokenLive"))+    except Exception:+        ap = {}+    if not aps_ready and stage != "skip_aps":+        return _out("aps_setup",+            "Next I'll turn on cloud search - it finds any design in your Autodesk cloud in about "+            "two seconds instead of a 30-minute folder crawl. It's a one-time sign-in.",+            "SET APS UP FOR THEM - do not merely mention it. (1) fusion_aps_status for exact state. "+            "(2) If not configured, an admin registers a PKCE app once, then fusion_aps_set_client_id. "+            "(3) fusion_aps_signin - drive it in their NATIVE browser (ABE), same reasoning as the "+            "Fusion sign-in: they're already logged into Autodesk there. (4) Poll fusion_aps_status "+            "until tokenLive:true, then call fusion_demo again - the demo then runs a LIVE sample "+            "search so they SEE the speed. If they decline setup, call fusion_demo {stage:'skip_aps'} "+            "and TELL them what they're missing (2s server-indexed search across the whole team hub; "+            "the old in-app search took 30+ min and crashed Fusion, so it is disabled). "+            "Skills: fusion-aps-search, fusion-aps-signin.",+            apsConfigured=bool(ap.get("configured")), apsSignedIn=bool(ap.get("signedIn")))++    # ── 4. APS sample search - let them SEE the speed ───────────────────────────+    aps_hits = []+    if aps_ready:+        try:+            import time as _t+            t0 = _t.time()+            sr = aps.handle_search({"query": demo_query or "board", "limit": 5}).get("data", {})+            aps_hits = [{"name": r.get("name"), "project": r.get("projectName")}+                        for r in (sr.get("results") or [])[:5]]+            steps.append("APS sample search: %d hits in %.1fs" % (len(aps_hits), _t.time() - t0))+        except Exception as e:+            steps.append("APS sample search failed: %s" % e)++    # ── 5. Open an ELECTRONICS design, then walk schematic -> 2D -> 3D ──────────+    st = _proxy_to_addin("get_app_state", {}, timeout=20) or {}+    doc = (st.get("data") or st).get("activeDocument")+    is_elec = bool((st.get("data") or st).get("isElectronics"))+    if not is_elec:+        target = demo_query or (aps_hits[0]["name"] if aps_hits else "")+        if not target:+            return _out("need_design",+                "I need an electronics design to show off. Which board should I open?",+                "No electronics design open and nothing to pick. If APS is live, "+                "fusion_aps_search {query:'<board>'} then fusion_aps_open {query:'<name>'}; else ask "+                "the user for a design name / open one via fusion_open_cloud_file. ALWAYS open the "+                "PROJECT (EcadDesignProductType), never a .brd/.sch/3D child. Then call fusion_demo again.")+        return _out("open_design",+            "Opening %s so you can see a real board end to end." % target,+            "OPEN THE PROJECT then re-call fusion_demo: fusion_aps_open {query:'%s'} (APS finds it at "+            "any folder depth in seconds). CRITICAL: open the electronics PROJECT file, not the "+            "schematic/.brd/3D child - a child opens an isolated, often empty view. Poll "+            "fusion_get_app_state until isElectronics:true, then call fusion_demo again." % target,+            target=target)++    steps.append("electronics project open: %s" % doc)+    _shot("project")++    # schematic -> 2D board -> 3D board, screenshotting each so the AI can SHOW them+    try:+        _proxy_to_addin("show_schematic", {}, timeout=90); steps.append("showed schematic"); _shot("schematic")+    except Exception as e:+        steps.append("schematic failed: %s" % e)+    try:+        _proxy_to_addin("show_2d_board", {}, timeout=90); steps.append("showed 2D board"); _shot("board_2d")+    except Exception as e:+        steps.append("2D board failed: %s" % e)+    try:+        _proxy_to_addin("show_3d_board", {}, timeout=180); steps.append("showed 3D board"); _shot("board_3d")+    except Exception as e:+        steps.append("3D board failed: %s" % e)++    aps_line = ("Cloud search is live - I searched your whole Autodesk hub in about two seconds and "+                "found: %s. " % ", ".join(h["name"] for h in aps_hits[:3])) if aps_hits else ""+    return _out("done",+        "That's the tour: your %s project, its schematic, the 2D board layout, and the real 3D board. "+        "%sFrom here just ask - export Gerbers/BOM/CPL for the fab, generate a laser-etched IPC "+        "package, load JLCPCB design rules, or open any design in your cloud." % (doc, aps_line),+        "DEMO COMPLETE. SHOW the user the screenshots in order (project, schematic, board_2d, "+        "board_3d) - they are in `screenshots` with labels. Narrate the `narrate` line. Then offer "+        "concrete next steps in THEIR words: 'export the Gerbers', 'make me a SOIC-8 with my part "+        "number etched on it' (fusion_generate_package), 'check this against JLCPCB rules' "+        "(fusion_load_design_rules), 'find my other boards' (fusion_aps_search). Full catalog: "+        "fusion_describe. Demo playbook: the fusion-demo skill.",+        done=True, document=doc, apsSampleSearch=aps_hits)++ def _orchestrate_board_stackup(args: dict) -> dict:     """Read a board's PHYSICAL fabrication stackup (see the pcb-stackup skill). A PCB is a     stack of copper + dielectric: Cu(L1) / prepreg / Cu(L2) / core / ... / Cu(bottom). This@@ -3782,6 +3953,8 @@ def dispatch_command(command: str, args: dict) -> dict:         return _orchestrate_export_optimized_glb(args)     if command == "fetch_optimized_glb":         return _orchestrate_fetch_optimized_glb(args)+    if command == "demo":+        return _orchestrate_demo(args)     if command == "board_stackup":         return _orchestrate_board_stackup(args) 
skills/fusion-demo/SKILL.mdadded+94
@@ -0,0 +1,94 @@+---+name: fusion-demo+description: >+  Run a FIRST-TIME-USER demo of the Fusion 360 bridge - the guided tour Hydrogen Desktop's installer+  fires when someone new wants to see what this thing does. Covers the one verb (fusion_demo), the+  staged loop, and the two rules that make or break it: FINISH the Autodesk sign-in (never leave a new+  user parked on the sign-in page) by driving the user's NATIVE browser via ABE/nbrowser_* (already+  signed into Autodesk) instead of pup (anonymous), and SET UP APS cloud search rather than just+  mentioning it, then prove it with a live sample search. Then show real work in order: electronics+  PROJECT -> SCHEMATIC -> 2D BOARD -> 3D BOARD. Trigger words - fusion demo, demo the bridge, show me+  fusion, first time user demo, hd installer demo, demo verb, guided tour, showcase fusion, new user+  onboarding demo, fusion_demo.+---++# Demoing the Fusion bridge to a brand-new user++## Why this exists++HD's installer ran a "demo" that opened Fusion and then **sat on the Autodesk sign-in page doing+nothing** (John, 2026-07-20). A first-time user watched an app open and stall. That is the single+worst first impression this bridge can make, and this skill exists to make it impossible.++**A demo is not "launch the app." A demo finishes the blockers and shows real work.**++## The one verb++```bash+adom-desktop fusion_demo '{}'                       # start / resume the tour+adom-desktop fusion_demo '{"query":"BQ25792"}'      # demo a specific design+adom-desktop fusion_demo '{"stage":"skip_aps"}'     # user declined cloud-search setup+```++It is **staged and resumable**. Every call advances as far as it safely can and returns:++| Field | Use it for |+|---|---|+| `stage` | where you are: `not_installed`, `signin`, `aps_setup`, `open_design`, `done`, ... |+| `done` | keep calling `fusion_demo` until this is `true` |+| `narrate` | say this to the user, verbatim-ish. It is written for a newcomer, not an engineer |+| `screenshots` | `[{label,path}]` - SHOW these; labels are `project`, `schematic`, `board_2d`, `board_3d` |+| `steps` | what it actually did, for your own logging |+| `_hint` | the exact next action, including the `nbrowser_*` calls for the browser leg |++Loop: call it, do what `_hint` says, narrate, call it again. Do not improvise a different order.++## Rule 1: FINISH the sign-in, in the user's NATIVE browser++When `stage:"signin"`:++- **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+  Autodesk**, so the same flow is one click. `nbrowser_profiles` lists their real profiles; ABE+  exposes these verbs even before the browser extension is installed.+- Click Fusion's **Sign In** in the BACKGROUND (`desktop_find_control` + `desktop_ui_click`), never a+  coordinate click, never steal focus.+- That fires the OAuth at the **default** browser. If that is the signed-in profile it completes+  itself; if it lands logged-out, re-open the auth URL with+  `nbrowser_open_window {profile:"chrome:<their account>"}`.+- The handoff back is the **Autodesk Identity Manager** overlay: tick "Always allow", click "Open".+- **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 minutes; if it expires, click Sign In again for+  a fresh one.++Full detail: the **fusion-autodesk-signin** skill.++## Rule 2: SET UP APS, don't just name-drop it++When `stage:"aps_setup"`, actually configure it (`fusion_aps_status` -> `fusion_aps_set_client_id`+if unconfigured -> `fusion_aps_signin` driven in the native browser -> poll until `tokenLive:true`).+Then the demo runs a **live sample search** so the user watches their whole Autodesk hub return+results in about two seconds.++If they decline, call `fusion_demo {stage:"skip_aps"}` and still TELL them what it is: server-indexed+search across the team hub in seconds. The old in-app search took 30+ minutes and crashed Fusion, so+it is disabled - APS is the only cloud-search path. See **fusion-aps-search**.++## Rule 3: the tour order is fixed++**electronics PROJECT -> SCHEMATIC -> 2D BOARD -> 3D BOARD.** That sequence tells the story of how a+board is actually made, and each step is screenshotted so you can show it. Always open the PROJECT+(`EcadDesignProductType`), never a `.brd`/`.sch`/3D child - a child opens an isolated, often empty+view and the demo looks broken.++## Landing it++Close by offering next steps in the user's own words, not verb names: "export the Gerbers", "make me+a SOIC-8 with my part number etched on it", "check this against JLCPCB rules", "find my other+boards". The full catalog is `fusion_describe`.++## If something is missing++- Fusion not installed -> offer the install, `fusion_install_fusion`, then resume (fusion-onboarding).+- Nothing to open -> ask which board, or `fusion_aps_search` and open the first hit.+- Never end a demo on an error screen. If a stage fails, say what you tried, and offer the next thing.