app
Adom Bridge
Public Made by Adomby adom
Adom Bridge unleashes your AI onto your full PC: full power, full safety. The AI breaks out of the container it runs in and onto your real machine, managing and running your entire laptop as you: launch apps, move files, drive any window, control KiCad, Fusion 360 or your real signed-in browser. Works with any AI, cloud or local (Claude Desktop, Claude Code, Codex), no Hydrogen required. Bridge and its bridges are an operating system for AI, with managed Node and Python runtimes, and a human-onl
← Commit history
sandbox-embed banner + Detect responsiveness + em-dash cleanup
1 file changed
+33−6
web-control-demo.html+33−6@@ -61,6 +61,13 @@ · ab: <span id="ver" class="pill">unknown</span> · web control: <span id="wc" class="pill">?</span> </div>+ <div id="sandbox-warn" style="display:none; margin:10px 0; padding:10px 14px; border:1px solid #f85149; border-radius:8px; background:rgba(248,81,73,.08); color:#f85149; font-size:14px;">+ <b>This embed cannot reach Adom Bridge.</b> The page is running in a sandboxed view (origin <code>null</code>)+ whose security policy blocks every fetch to <code>127.0.0.1</code>, so ALL buttons here will fail with+ “Failed to fetch”. Open the page directly instead:+ <a href="http://127.0.0.1:24681/" target="_blank" rel="noopener" style="color:#58a6ff">http://127.0.0.1:24681/</a>+ (any real browser tab works).+ </div> <div class="bar"> <label for="target">Run on machine:</label>@@ -163,6 +170,16 @@ for (const w of mine) { var dot = document.getElementById("dot"); var sel = document.getElementById("target"); document.getElementById("origin").textContent = location.origin || "(null)";+ // A sandboxed embed (wiki readme iframe, an in-app webview) runs at origin+ // "null" and its CSP blocks EVERY loopback fetch - the whole page is dead+ // there. Say so up front instead of letting each button fail cryptically.+ var SANDBOXED = !location.origin || location.origin === "null";+ function sandboxWarn() { document.getElementById("sandbox-warn").style.display = "block"; }+ if (SANDBOXED) sandboxWarn();+ // Any fetch that dies with the browser's opaque TypeError (CSP block, no+ // listener, mixed-content) escalates the banner too - the single most+ // common cause is the sandboxed-embed case above.+ function fetchDied(e) { if (/failed to fetch|networkerror/i.test(String(e))) sandboxWarn(); } function logLine(m) { log.textContent += "\n" + m; log.scrollTop = log.scrollHeight; } function chosenTarget() { return sel.value || null; } // WHO is "this machine": ab 2.0.53+ reports its relay client name at@@ -205,7 +222,7 @@ for (const w of mine) { logLine("GET " + AB + path); fetch(AB + path).then(function (r) { return r.json().then(function (j) { return { s: r.status, j: j }; }); }) .then(function (res) { logLine(" <- HTTP " + res.s); if (res.s === 200) setRes(el, "ok", "OK", summarize ? summarize(res.j) : ""); else setRes(el, "err", "HTTP " + res.s, ""); })- .catch(function (e) { setRes(el, "err", "unreachable", String(e)); });+ .catch(function (e) { fetchDied(e); setRes(el, "err", "unreachable", SANDBOXED ? "sandboxed embed blocks loopback - open http://127.0.0.1:24681 directly (see banner)" : String(e)); }); } function doVerb(el, command, args, target, summarize) { var body = { command: command }; if (args) body.args = args;@@ -221,7 +238,7 @@ for (const w of mine) { else if (res.j && res.j.errorCode === "web_control_disabled") setRes(el, "err", "Refused: web control OFF", "re-enable in Settings → Web Control"); else setRes(el, "err", "Not approved", (res.j && (res.j.error || res.j.errorCode)) || ("HTTP " + res.s)); })- .catch(function (e) { setRes(el, "err", "blocked", String(e)); });+ .catch(function (e) { fetchDied(e); setRes(el, "err", "blocked", SANDBOXED ? "sandboxed embed blocks loopback - open http://127.0.0.1:24681 directly (see banner)" : String(e)); }); } // POST that returns the flattened JSON. Optional target. function postVerb(command, args, target) {@@ -503,7 +520,7 @@ for (const w of mine) { var want = pickTitle.toLowerCase(); var picked = withImg.filter(function (s) { return ((s.title || s.label || "") + "").toLowerCase().indexOf(want) >= 0; }); if (picked.length) { withImg = picked; }- else clog(c, ' (no window titled like "' + pickTitle + '" — showing all ' + withImg.length + ')');+ else clog(c, ' (no window titled like "' + pickTitle + '" - showing all ' + withImg.length + ')'); } clog(c, "Rendering " + withImg.length + " window(s)."); withImg.forEach(function (s) { addShot(c.card, s.image || s.base64 || s.png, s.title || s.label || s.window || "window"); });@@ -650,7 +667,7 @@ for (const w of mine) { var on = wc.enabled !== false; el.textContent = on ? "ON" : "OFF"; el.style.color = on ? "#2ea043" : "#f85149"; setRes(cDetect.res, "ok", "Running: v" + (s.version || "?"), "web control " + (on ? "ON" : "OFF")); });- }).catch(function (e) { dot.style.background = "#484f58"; setRes(cDetect.res, "err", "not reachable", String(e)); });+ }).catch(function (e) { fetchDied(e); dot.style.background = "#484f58"; setRes(cDetect.res, "err", "not reachable", SANDBOXED ? "sandboxed embed blocks loopback - open http://127.0.0.1:24681 directly (see banner)" : String(e)); }); }; var cStatus = card("grp-read", "read", "Read status"); cStatus.btn.onclick = function () { doGet(cStatus.res, "/status", function (s) { var st = n(s.bridgeUpdates && s.bridgeUpdates.staleCount); return "v" + s.version + " - web control " + ((s.webControl && s.webControl.enabled === false) ? "OFF" : "ON") + (st ? (" - " + st + " bridge update(s)") : ""); }); };@@ -798,7 +815,7 @@ for (const w of mine) { { label: "Library (" + PART.name + " in '" + PART.kicadLib + "')", cmd: "kicad_show_library", args: function () { return { libraryName: PART.kicadLib }; }, stage: stageKicadPart, pick: "symbol editor", note: "the library holding the symbol + footprint + 3D model" }, { label: "Schematic (just the " + PART.name + ")", cmd: "kicad_show_schematic", args: function () { return { filePath: (PART.kicadProject || {}).schPath }; }, stage: stageKicadProject, pick: "schematic editor", note: "the generated single-component schematic (kicad_make_part_project)" }, { label: "2D board (part placed, tight edge cuts)", cmd: "kicad_show_2d_board", args: function () { return { filePath: (PART.kicadProject || {}).pcbPath }; }, stage: stageKicadProject, pick: "pcb editor", note: "the part-dominant board" },- { label: "3D board (real 3D body on the board)", cmd: "kicad_show_3d_board", args: function () { return { filePath: (PART.kicadProject || {}).pcbPath }; }, stage: stageKicadProject, pick: "3d viewer", note: "the populated board in 3D — the whole ladder underneath it" },+ { label: "3D board (real 3D body on the board)", cmd: "kicad_show_3d_board", args: function () { return { filePath: (PART.kicadProject || {}).pcbPath }; }, stage: stageKicadProject, pick: "3d viewer", note: "the populated board in 3D - the whole ladder underneath it" }, { label: "Project (top of the tree)", cmd: "kicad_show_project", args: function () { return { filePath: (PART.kicadProject || {}).proPath }; }, stage: stageKicadProject, pick: "kicad 10", note: "the Project Manager: schematic + board hang off it" } ].forEach(function (s) { var cc = card("grp-kicad", "verb", s.label);@@ -939,6 +956,10 @@ for (const w of mine) { // ---- target picker ---- document.getElementById("refresh-targets").onclick = function () {+ var btn = this, pill = document.getElementById("target-count");+ // The user must SEE the click did something, instantly - and see the+ // failure in the pill, not buried in the collapsed log (John, 2026-08-16).+ btn.disabled = true; pill.style.color = ""; pill.textContent = "detecting ... (approve the Adom Bridge prompt if one appears)"; logLine("POST /command {targets}"); // Resolve the self name first so the dedupe + "self" label are right even // when Detect was never clicked (an old ab just returns no machine block).@@ -946,6 +967,7 @@ for (const w of mine) { .then(function () { return fetch(AB + "/command", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ command: "targets" }) }); }) .then(function (r) { return r.json(); }) .then(function (j) {+ btn.disabled = false; // The direct API answers with clients[]; the relay-CLI shape is targets[]. var d = j.data || j; var list = d.targets || d.clients || []; // The relay lists THIS machine by name too - that row is redundant with@@ -959,7 +981,12 @@ for (const w of mine) { if (peers.length >= 1) { var a = document.createElement("option"); a.value = "all"; a.textContent = "ALL machines (fan out)"; sel.appendChild(a); } document.getElementById("target-count").textContent = selfLabel() + (peers.length ? " + " + peers.length + " more" : " only"); })- .catch(function (e) { logLine("targets failed: " + e); });+ .catch(function (e) {+ btn.disabled = false; fetchDied(e);+ pill.style.color = "#f85149";+ pill.textContent = SANDBOXED ? "blocked: sandboxed embed (see banner)" : ("detect failed: " + String(e).slice(0, 80));+ logLine("targets failed: " + e);+ }); }; })(); </script>