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
Update 13 file(s)
9 files changed
+92−60
BRIDGE_VERSION+1−1@@ -1 +1 @@-1.9.178+1.9.179
adom-bridge-fusion-manifest.json+3−3@@ -1,9 +1,9 @@ { "manifest_version": 1, "name": "fusion360",- "version": "1.9.178",- "url": "https://wiki.adom.inc/download/adom/fusion-bridge/1.9.178/adom-bridge-fusion-v1.9.178.zip",- "sha256": "9e6b0481950310332991ee3d1c60ee1a98d095c96b74c0a55264e35008e9d8be",+ "version": "1.9.179",+ "url": "https://wiki.adom.inc/download/adom/fusion-bridge/1.9.179/adom-bridge-fusion-v1.9.179.zip",+ "sha256": "c1cc8f9990189dcbe592b503f7fe77cfefbb04de6e65b250e841a7bef3db59f4", "size": 484687, "verbPrefixes": [ "fusion_"
bridge.json+1−1@@ -2,7 +2,7 @@ "manifest_version": 1, "name": "fusion360", "displayName": "Autodesk Fusion 360",- "version": "1.9.178",+ "version": "1.9.179", "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/fusion-bridge", "author": "Adom Inc.",
dashboard/Cargo.lock+1−1@@ -4,7 +4,7 @@ version = 4 [[package]] name = "fusion-dashboard"-version = "1.9.178"+version = "1.9.179" dependencies = [ "serde_json", ]
dashboard/Cargo.toml+1−1@@ -1,6 +1,6 @@ [package] name = "fusion-dashboard"-version = "1.9.178"+version = "1.9.179" edition = "2021" [dependencies]
dashboard/src/main.rs+72−43@@ -1262,55 +1262,84 @@ fn url_for(port: u16) -> String { } /// Stage a "say it" phrase into this app's AI thread via the Hydrogen control API-/// (127.0.0.1:47084). Thread found by its `app:adom/fusion-bridge` tag; run-prompt is-/// called with submit:false so the phrase only lands in the input box - the user submits.+/// (127.0.0.1:47084). Lookup order per the Dock Bar guide (project-e7, measured live+/// 2026-08-23): CENSUS FIRST (GET /ai-threads - the OPEN threads; history EXCLUDES open+/// threads by design, so history-only returned empty in exactly the common case), then+/// HISTORY for closed threads (resume, then stage). run-prompt is always submit:false -+/// the phrase only lands in the input box, the user submits. /// Every failure is a graceful {ok:false, why} the UI turns into a copy fallback.-fn stage_say_phrase(phrase: &str) -> serde_json::Value {- let hy = "http://127.0.0.1:47084";- // 1. find the thread sid by app tag+fn hy_get(path: &str) -> Option<serde_json::Value> { let out = Command::new("curl")- .args(["-s", "-m", "2",- &format!("{}/ai-threads/history?tag=app:adom/fusion-bridge", hy)])- .output();- let hist = match out {- Ok(o) if !o.stdout.is_empty() => String::from_utf8_lossy(&o.stdout).to_string(),- _ => return serde_json::json!({"ok": false, "why": "Hydrogen control API unreachable (not WSL2/mirrored networking?)"}),- };- let v: serde_json::Value = match serde_json::from_str(&hist) {- Ok(v) => v,- Err(_) => return serde_json::json!({"ok": false, "why": "control API answered non-JSON"}),- };- // The sid may sit at several depths depending on the endpoint's shape - take the first- // "sid" string found anywhere (same envelope-drift defense as the bridge's _find_controls_any).- fn first_sid(v: &serde_json::Value) -> Option<String> {- match v {- serde_json::Value::Object(m) => {- if let Some(s) = m.get("sid").and_then(|s| s.as_str()) {- return Some(s.to_string());- }- m.values().find_map(first_sid)- }- serde_json::Value::Array(a) => a.iter().find_map(first_sid),- _ => None,- }- }- let sid = match first_sid(&v) {- Some(s) => s,- None => return serde_json::json!({"ok": false, "why": "no AI thread tagged app:adom/fusion-bridge yet - open the app from the dock once"}),- };- // 2. stage the phrase, never submit- let payload = serde_json::json!({"sid": sid, "prompt": phrase, "submit": false}).to_string();+ .args(["-s", "-m", "2", &format!("http://127.0.0.1:47084{}", path)])+ .output().ok()?;+ if out.stdout.is_empty() { return None; }+ serde_json::from_slice(&out.stdout).ok()+}++fn hy_post(path: &str, payload: &serde_json::Value) -> Option<serde_json::Value> { let out = Command::new("curl") .args(["-s", "-m", "3", "-X", "POST", "-H", "Content-Type: application/json",- "-d", &payload, &format!("{}/ai-threads/run-prompt", hy)])- .output();- match out {- Ok(o) if o.status.success() && !o.stdout.is_empty() => {- serde_json::json!({"ok": true, "staged": true, "sid": sid,- "_hint": "phrase staged in the app's AI thread input - the user presses Enter to run it"})+ "-d", &payload.to_string(), &format!("http://127.0.0.1:47084{}", path)])+ .output().ok()?;+ if out.stdout.is_empty() { return None; }+ serde_json::from_slice(&out.stdout).ok()+}++fn stage_say_phrase(phrase: &str) -> serde_json::Value {+ const TAG: &str = "app:adom/fusion-bridge";+ // 1. CENSUS: open threads. Shape: {"ok":true,"groups":[{"threads":[{"tid","sid"|null,"tags":[...]}]}]}+ let census = hy_get("/ai-threads");+ if census.is_none() {+ return serde_json::json!({"ok": false,+ "why": "Hydrogen control API unreachable (not WSL2/mirrored networking?)"});+ }+ let census = census.unwrap();+ let open_row = census["groups"].as_array().into_iter().flatten()+ .flat_map(|g| g["threads"].as_array().cloned().unwrap_or_default())+ .find(|t| t["tags"].as_array().map_or(false,+ |tags| tags.iter().any(|x| x.as_str() == Some(TAG))));+ if let Some(row) = open_row {+ // Address by sid when present; tid when sid is null (thread open but no session yet).+ let mut payload = serde_json::json!({"prompt": phrase, "submit": false});+ if let Some(sid) = row["sid"].as_str() {+ payload["sid"] = serde_json::json!(sid);+ } else if let Some(tid) = row["tid"].as_str() {+ payload["tid"] = serde_json::json!(tid);+ } else {+ return serde_json::json!({"ok": false, "why": "census row has neither sid nor tid"});+ }+ return match hy_post("/ai-threads/run-prompt", &payload) {+ Some(r) if r["ok"].as_bool() != Some(false) =>+ serde_json::json!({"ok": true, "staged": true, "via": "census",+ "_hint": "phrase staged in the open app thread's input - the user presses Enter"}),+ _ => serde_json::json!({"ok": false, "why": "run-prompt failed on the open thread"}),+ };+ }+ // 2. HISTORY: closed threads. Shape: {"ok":true,"items":[{"ai","id","title","updated_ms"}]}+ let hist = hy_get(&format!("/ai-threads/history?tag={}", TAG));+ if let Some(item) = hist.as_ref()+ .and_then(|h| h["items"].as_array())+ .and_then(|a| a.first()) {+ let (ai, id) = (item["ai"].as_str().unwrap_or("claude"),+ item["id"].as_str().unwrap_or(""));+ if !id.is_empty() {+ // Resume reopens the thread tab - the user clicked Try, so that IS their intent.+ let resumed = hy_post("/ai-threads/resume", &serde_json::json!({"ai": ai, "id": id}));+ if resumed.map_or(false, |r| r["ok"].as_bool() != Some(false)) {+ let staged = hy_post("/ai-threads/run-prompt",+ &serde_json::json!({"sid": id, "prompt": phrase, "submit": false}));+ if staged.map_or(false, |r| r["ok"].as_bool() != Some(false)) {+ return serde_json::json!({"ok": true, "staged": true, "via": "history+resume",+ "_hint": "reopened the app thread and staged the phrase - the user presses Enter"});+ }+ }+ return serde_json::json!({"ok": false,+ "why": "app thread exists but is closed and resume/stage failed"}); }- _ => serde_json::json!({"ok": false, "why": "run-prompt call failed"}), }+ // 3. Neither: honest copy fallback.+ serde_json::json!({"ok": false,+ "why": "no AI thread tagged app:adom/fusion-bridge yet - open the app from the dock once"}) } /// Probe 8930-8940 for an already-serving FUSION dashboard. "dashboardVersion" alone is
install.sh+1−1@@ -21,7 +21,7 @@ echo "Installed fusion-bridge skills: $(ls "$PKG_DIR/skills" 2>/dev/null | tr '\ # binary (the three-artifact pattern: bridge zip -> ab, THIS pkg -> container skills + # this download, page media -> wiki). Per the wiki's PKG_VS_RELEASE policy, binaries are # platform-tagged RELEASE assets, never tarball content - the installer fetches them.-DASH_VERSION="1.9.178"+DASH_VERSION="1.9.179" DASH_URL="https://wiki.adom.inc/download/adom/fusion-bridge/${DASH_VERSION}/fusion-dashboard" DEST="" if [ -w /usr/local/bin ]; then DEST=/usr/local/bin
package.json+1−1@@ -1,6 +1,6 @@ { "slug": "fusion-bridge",- "version": "1.9.178",+ "version": "1.9.179", "type": "app", "description": "This package installs the bridge's SKILLS into your container so your AI knows how to drive it; Adom Bridge loads the bridge runtime itself from the release zip.", "dependencies": {
page.json+11−8@@ -1,6 +1,6 @@ { "slug": "fusion-bridge",- "version": "1.9.178",+ "version": "1.9.179", "type": "app", "description": "This package installs the bridge's SKILLS into your container so your AI knows how to drive it; Adom Bridge loads the bridge runtime itself from the release zip.", "dependencies": {@@ -11,6 +11,10 @@ "uninstall": "./uninstall.sh" }, "tags": [+ "ab",+ "bridge",+ "fusion",+ "desktop-app", "fusion360", "autodesk", "cad",@@ -75,7 +79,10 @@ "build a basic parts library", "add a 3d model to a component", "make a fusion 360 library with 3d",- "set up the fusion bridge"+ "set up the fusion bridge",+ "fusion dashboard",+ "fusion status page",+ "open the fusion launch page" ], "discovery_pitch": "Use the Adom Bridge 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 Bridge. 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 Bridge: component libraries, IPC package generation, board layout, exports (STEP/Gerbers/BOM/CPL), fast APS cloud search, and parametric modeling.",@@ -104,9 +111,5 @@ "label": "Part volume", "prompt": "How much material does each printed part in this assembly use?" }- ],- "author": {- "name": "John Lauer",- "email": "[email protected]"- }-}+ ]+}