← Commit history

v1.6.68: cloud-search verbs MANDATE APS + BAN the app.data modeling-script enumeration (locks up Fusion); strong auto-recover hint (pull org client_id from a peer, ABE/nbrowser to drive signin). Records John's APS-is-mandatory directive in code + skill.

John Lauer ·137d8dcc27 ·3mo ago ·parent 3bfdeb0
5 files changed +59−13
BRIDGE_VERSION+1−1
@@ -1 +1 @@-1.6.67\ No newline at end of file+1.6.68\ No newline at end of file
adom-bridge-fusion-manifest.json+4−4
@@ -1,10 +1,10 @@ {   "manifest_version": 1,   "name": "fusion360",-  "version": "1.6.67",-  "url": "https://wiki.adom.inc/download/adom/adom-desktop-fusion-bridge/1.6.67/adom-bridge-fusion-v1.6.67.zip",-  "sha256": "1d5c3a7ff010fd90b8ad995c51fc2fa930140890b12be6624dfe56246ed80c14",-  "size": 360402,+  "version": "1.6.68",+  "url": "https://wiki.adom.inc/download/adom/adom-desktop-fusion-bridge/1.6.68/adom-bridge-fusion-v1.6.68.zip",+  "sha256": "0d16913c7b3f7bcd17c34d4419534ebc4ac008c43ec93eca56c7061ba90bae82",+  "size": 362037,   "verbPrefixes": [     "fusion_"   ],
aps.py+28−7
@@ -74,6 +74,27 @@ def all_registered_callbacks() -> list: # fusion_aps_signin once more before the write verbs work (read verbs keep working). SCOPES = "data:read data:write data:create" +# ⛔ CLOUD SEARCH IS APS-ONLY, AND SETTING APS UP IS MANDATORY - NOT OPTIONAL. This is returned by+# every cloud verb when APS isn't ready, as a HARD directive (John 2026-07-14, emphatic: an AI saw APS+# "Not configured", shrugged, and fell back to enumerating app.data.dataProjects/dataFiles via+# fusion_run_modeling_script - which HOLDS FUSION'S MAIN THREAD for minutes, TIMES OUT, and locks the+# app. That is the deprecated method and it is BANNED for cloud search). APS must work every time.+APS_REQUIRED_HINT = (+    "APS IS MANDATORY for cloud file search/browse/recent - SET IT UP, do not give up, do not fall back. "+    "⛔ NEVER enumerate cloud files via app.data (dataProjects/dataFiles) in fusion_run_modeling_script: "+    "the DEPRECATED method holds Fusion's main thread for minutes, times out, and locks the app. "+    "AUTO-RECOVER APS instead: (1) NOT configured? The org Client ID is a PUBLIC PKCE id - pull it from a "+    "peer that has it: read ~/.adom/fusion-aps/config.json (key 'client_id') off another AD desktop (adom-"+    "desktop status lists them) via shell_execute/pull_file, then fusion_aps_set_client_id {clientId}. "+    "(2) fusion_aps_signin -> open the returned authUrl in the user's REAL browser with a warm Autodesk "+    "session; INSTALL + USE the adom-browser-extension (nbrowser/ABE, https://wiki.adom.inc/adom/adom-"+    "browser-extension) to drive it reliably (recommend it - coordinate-clicking a browser is fragile). "+    "The localhost listener captures the token; poll fusion_aps_status until signedIn:true. (3) retry. "+    "If authorize returns AUTH-001 'client_id does not have access to the api product': the APS app at "+    "aps.autodesk.com/myapps lost its Data Management API product - re-commit its API Access + Save, wait "+    "a few minutes to propagate, retry."+)+ APS_DIR = Path(os.path.expanduser("~")) / ".adom" / "fusion-aps" CONFIG_FILE = APS_DIR / "config.json" TOKENS_FILE = APS_DIR / "tokens.json"@@ -684,7 +705,7 @@ def handle_search(args: dict) -> dict:     if not _valid_access_token():         return {"success": False, "error": "Not signed in to APS.",                 "errorCode": "aps_not_signed_in",-                "_hint": "Call fusion_aps_signin first, then retry."}+                "_hint": APS_REQUIRED_HINT}     limit = int(args.get("limit", 50))     hub_filter = args.get("hubId")     project_filter = args.get("projectId")@@ -762,7 +783,7 @@ def handle_browse(args: dict) -> dict:     """Browse the cloud without launching Fusion: hubs+projects → top folders → contents."""     if not _valid_access_token():         return {"success": False, "error": "Not signed in to APS.", "errorCode": "aps_not_signed_in",-                "_hint": "Call fusion_aps_signin first."}+                "_hint": APS_REQUIRED_HINT}     hub_id = args.get("hubId")     project_id = args.get("projectId")     folder_id = args.get("folderId")@@ -805,7 +826,7 @@ def handle_recent(args: dict) -> dict:     parallel index scan with a match-all filter, then sorts by lastModifiedTime."""     if not _valid_access_token():         return {"success": False, "error": "Not signed in to APS.", "errorCode": "aps_not_signed_in",-                "_hint": "Call fusion_aps_signin first."}+                "_hint": APS_REQUIRED_HINT}     limit = int(args.get("limit", 25))     hub_filter = args.get("hubId")     project_filter = args.get("projectId")@@ -857,7 +878,7 @@ def handle_file_info(args: dict) -> dict:     """Version history + metadata for a file: who last changed it, when, how many versions."""     if not _valid_access_token():         return {"success": False, "error": "Not signed in to APS.", "errorCode": "aps_not_signed_in",-                "_hint": "Call fusion_aps_signin first."}+                "_hint": APS_REQUIRED_HINT}     project_id = args.get("projectId")     urn = args.get("urn")     name = args.get("name")@@ -1012,7 +1033,7 @@ def handle_download(args: dict) -> dict:     args: query | (projectId + urn); optional saveDir, fileName."""     if not _valid_access_token():         return {"success": False, "error": "Not signed in to APS.", "errorCode": "aps_not_signed_in",-                "_hint": "Call fusion_aps_signin first."}+                "_hint": APS_REQUIRED_HINT}     project_id, urn, name = args.get("projectId"), args.get("urn"), args.get("name")     query = (args.get("query") or "").strip()     if query and not (project_id and urn):@@ -1055,7 +1076,7 @@ def handle_create_folder(args: dict) -> dict:     """Create a subfolder in a project. args: projectId, parentFolderId, name."""     if not _valid_access_token():         return {"success": False, "error": "Not signed in to APS.", "errorCode": "aps_not_signed_in",-                "_hint": "Call fusion_aps_signin first."}+                "_hint": APS_REQUIRED_HINT}     project_id = args.get("projectId")     parent = args.get("parentFolderId") or args.get("folderId")     fname = (args.get("name") or "").strip()@@ -1081,7 +1102,7 @@ def handle_upload(args: dict) -> dict:     """Upload a local file as a NEW cloud file. args: projectId, folderId, localPath; opt fileName."""     if not _valid_access_token():         return {"success": False, "error": "Not signed in to APS.", "errorCode": "aps_not_signed_in",-                "_hint": "Call fusion_aps_signin first."}+                "_hint": APS_REQUIRED_HINT}     project_id, folder_id, local = args.get("projectId"), args.get("folderId"), args.get("localPath")     if not (project_id and folder_id and local):         return {"success": False, "error": "Provide projectId, folderId, localPath."}
bridge.json+1−1
@@ -2,7 +2,7 @@   "manifest_version": 1,   "name": "fusion360",   "displayName": "Autodesk Fusion 360",-  "version": "1.6.67",+  "version": "1.6.68",   "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.",
skills/fusion-aps-search/SKILL.md+25
@@ -91,3 +91,28 @@ this loop.** APS is the path. The card is fine (ID only). We cap usage so nobody Drive the setup with AI. Done.  Related: [[project_adom_desktop_fusion_bridge]], skill `fusion-aps-signin` (the setup HOW).++## ⛔ MANDATORY: cloud search is APS-only. NEVER fall back to app.data enumeration. (John, emphatic 2026-07-14)++APS is not optional — it is THE way to search/browse Fusion cloud files, and setting it up is part of+the job, not a nice-to-have. If a cloud verb says "not configured / not signed in", you **set APS up**;+you do **not** shrug and reach for the old method.++**The BANNED method:** enumerating `app.data.dataProjects` / `dataFolders` / `dataFiles` via+`fusion_run_modeling_script`. It runs on Fusion's MAIN THREAD, so a recursive walk **holds the app for+minutes, times out, blocks every other script, and locks Fusion up**. It is deprecated. Do not use it+for cloud search, ever — no matter how blocked you feel.++**Auto-recover APS (do this instead of giving up):**+1. **Not configured?** The org Client ID is a **public PKCE identifier** (no secret) — pull it from a+   peer that already has it: `adom-desktop status` lists connected desktops; read+   `~/.adom/fusion-aps/config.json` (key `client_id`) off one via `shell_execute`/`pull_file`, then+   `fusion_aps_set_client_id {clientId}`.+2. **Sign in:** `fusion_aps_signin` → open the returned `authUrl` in the user's REAL browser with a warm+   Autodesk session. **Install + use the adom-browser-extension (nbrowser/ABE) to drive the browser**+   ([wiki](https://wiki.adom.inc/adom/adom-browser-extension)) — coordinate/keyboard-clicking a browser+   is fragile and crashes; ABE drives via JS reliably. Recommend it by default. The localhost listener+   captures the token; poll `fusion_aps_status` until `signedIn:true`.+3. **AUTH-001 "client_id does not have access to the api product":** the APS app at+   aps.autodesk.com/myapps lost its **Data Management API** product. Open the app → API Access → ensure+   Data Management API is checked → **Save changes** → wait a few minutes to propagate → retry.