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
web-control-demo.html+94−13@@ -78,6 +78,12 @@ <div class="grp">Do something - each asks permission, streams live status, then captures the app's FULL window state</div> <div class="cards" id="grp-do"></div> + <div class="grp">KiCad surfaces - one first-class verb per surface (kicad_show_*, runtime 0.9.96+). Each opens in the BACKGROUND, then we render it from kicad_state.</div>+ <div class="cards" id="grp-kicad"></div>++ <div class="grp">Fusion surfaces - one first-class verb per content type (fusion_show_*, v1.9.10+). Design surfaces need a project open; library surfaces need a library open (the tour above stages its own).</div>+ <div class="cards" id="grp-fusion"></div>+ <div class="grp">Multi-machine - fan a verb across every connected computer</div> <div class="cards" id="grp-multi"></div> @@ -375,6 +381,15 @@ for (const w of mine) { c.btn.disabled = false; return; } var est = res.j.estimatedSec || 100;+ // Thread an id from the START response into the poll args when the bridge+ // hands one back (e.g. Fusion's fusion_demo returns a demoId that+ // fusion_demo_progress needs). KiCad's poll is stateless, so no idFrom.+ var pollArgs = Object.assign({}, cfg.pollArgs || { progress: true });+ if (cfg.idFrom) {+ var idVal = res.j[cfg.idFrom];+ if (idVal == null && res.j.data) idVal = res.j.data[cfg.idFrom];+ if (idVal != null) { pollArgs[cfg.idAs || cfg.idFrom] = idVal; clog(c, " tracking " + (cfg.idAs || cfg.idFrom) + "=" + idVal); }+ } clog(c, "Started (bridge estimates ~" + est + "s). Polling for live progress ..."); setProgress(c.card, res.j.percent || 2, "preparing ...", 0, 0, 0, est); setRes(c.res, "pending", "running the demo ...", "usually ~" + est + "s - watch the bar");@@ -383,7 +398,7 @@ for (const w of mine) { function poll() { polls++; if (polls > maxPolls) { setRes(c.res, "err", "timed out", "the demo did not finish in time"); c.btn.disabled = false; return; }- postVerb(cfg.pollCmd || cfg.startCmd, cfg.pollArgs || { progress: true }).then(function (pr) {+ postVerb(cfg.pollCmd || cfg.startCmd, pollArgs).then(function (pr) { if (pr.s !== 200 || !pr.j) { return new Promise(function (r) { setTimeout(r, 2500); }).then(poll); } var d = pr.j; if (d.error && d.active === undefined && d.done === undefined) {@@ -447,6 +462,36 @@ for (const w of mine) { return true; } + // Open ONE surface via a bridge's first-class show verb, then render it from the+ // bridge's state verb. The show verbs open in the BACKGROUND by design, so we+ // SHOW the window by screenshotting state - we never foreground the app. On a+ // prerequisite miss the bridge returns a _hint; we surface it.+ function showSurface(c, cfg) {+ c.btn.disabled = true;+ cardLog(c.card).textContent = "";+ var sb = c.card.querySelector("div.shots"); if (sb) sb.innerHTML = "";+ setRes(c.res, "pending", "asking permission (choose “Allow 1 hr”) ...", cfg.note || "opens in the background, then we screenshot it");+ clog(c, "Calling " + cfg.showCmd + " " + JSON.stringify(cfg.showArgs || {}) + (cfg.note ? " (" + cfg.note + ")" : ""));+ postVerb(cfg.showCmd, cfg.showArgs || {}).then(function (res) {+ if (res.j && res.j._hint) clog(c, " bridge hint: " + res.j._hint);+ var ok = res.s === 200 && res.j && (res.j.success === true || res.j.ok === true || res.j.surface || res.j.shots || res.j.result || res.j.state);+ if (!ok) {+ if (res.j && res.j.errorCode === "web_control_disabled") setRes(c.res, "err", "web control OFF", "re-enable in Settings → Web Control");+ else setRes(c.res, "err", "not opened", (res.j && (res.j.error || res.j.errorCode || res.j._hint)) || ("HTTP " + res.s));+ c.btn.disabled = false; return;+ }+ clog(c, " opened" + (res.j.surface ? " (surface: " + res.j.surface + ")" : "") + " - capturing state ...");+ // The show verb MAY already carry shots; else pull the state verb.+ if (renderShots(c, res.j)) return;+ return postVerb(cfg.stateCmd, { maxWidth: 900 }).then(function (sr) {+ if (!(sr.s === 200 && sr.j && renderShots(c, sr.j))) {+ setRes(c.res, "ok", "Opened" + (res.j.surface ? " (" + res.j.surface + ")" : ""), "no window captured yet (may need a prerequisite - see the hint)");+ c.btn.disabled = false;+ }+ });+ }).catch(function (e) { setRes(c.res, "err", "blocked", String(e)); c.btn.disabled = false; });+ }+ // summaries function n(x) { return (x === undefined || x === null) ? 0 : x; } function bridgesSummary(j) { var d = j.data || j; var a = d.bridges || []; return a.length + " bridge(s): " + a.map(function (b) { return b.name; }).slice(0, 6).join(", "); }@@ -493,20 +538,56 @@ for (const w of mine) { stateCmd: "kicad_state" }); };- var cFusion = card("grp-do", "verb", "Open Fusion 360");+ var cFusion = card("grp-do", "verb", "Run the Fusion demo (bridge-driven, live progress)"); cFusion.btn.onclick = function () {- streamRecipe(cFusion, [- { command: "fusion_start", args: {}, label: "Launching Autodesk Fusion", usuallySec: 90 },- { waitForWindow: { titleContains: "Fusion", timeoutSec: 280 }, label: "Waiting for Fusion to come up", usuallySec: 120 }- ], { include: ["fusion", "autodesk"], exclude: ["for testing", "harness"] });- };- var cSym = card("grp-do", "verb", "Open KiCad (Symbol Editor)");- cSym.btn.onclick = function () {- streamRecipe(cSym, [- { command: "kicad_open_symbol_editor", args: {}, label: "Opening the KiCad Symbol Editor", usuallySec: 30 },- { waitForWindow: { titleContains: "Symbol Editor", timeoutSec: 90 }, label: "Waiting for the Symbol Editor", usuallySec: 8 }- ], { include: ["kicad", "symbol editor", "pcb editor", "schematic editor"], exclude: ["for testing", "harness", "adom bridge web", "adom wiki"] });+ // The Fusion bridge OWNS this demo (fusion_demo, v1.9.9+): an async tour over+ // symbol / footprint / device / 3D-package (+ schematic/2D/3D board) off its+ // bundled sample library. Start returns a demoId that fusion_demo_progress+ // needs, and the final poll carries the screenshot array. Same harness as+ // KiCad, just a demoId to thread through.+ runBridgeDemo(cFusion, {+ startCmd: "fusion_demo", startArgs: { mode: "tour" },+ pollCmd: "fusion_demo_progress", pollArgs: {},+ idFrom: "demoId", idAs: "demoId",+ stateCmd: "fusion_state"+ }); };+ // ---- KiCad surfaces: one first-class verb per surface (kicad_show_*) ----+ // Zero-setup surfaces hit KiCad's STOCK libraries (nothing to push, solid cold).+ // Design-file surfaces point at the project the KiCad demo generates (run it+ // first). 3D surfaces are WARM-ONLY - reliable once KiCad is open (kicad-bridge #26).+ var KDEMO = "%USERPROFILE%\\Documents\\adom-kicad-demo\\adom-demo";+ [+ { label: "Symbol (R, Device lib)", cmd: "kicad_show_symbol", args: { symbolName: "R", libraryName: "Device" }, note: "stock library, works cold" },+ { label: "Footprint (R_0603, Resistor_SMD)", cmd: "kicad_show_footprint", args: { footprintName: "R_0603_1608Metric", library: "Resistor_SMD" }, note: "stock library, works cold" },+ { label: "Library browser (Device)", cmd: "kicad_show_library", args: { libraryName: "Device" }, note: "stock library, works cold" },+ { label: "Project (Project Manager)", cmd: "kicad_show_project", args: { filePath: KDEMO + ".kicad_pro" }, note: "run the KiCad demo first to generate this project" },+ { label: "Schematic", cmd: "kicad_show_schematic", args: { filePath: KDEMO + ".kicad_sch" }, note: "run the KiCad demo first" },+ { label: "2D board", cmd: "kicad_show_2d_board", args: { filePath: KDEMO + ".kicad_pcb" }, note: "run the KiCad demo first" },+ { label: "3D chip (warm-only)", cmd: "kicad_show_3d_chip", args: { footprintName: "R_0603_1608Metric", library: "Resistor_SMD" }, note: "run the KiCad demo first; 3D window is flaky cold" },+ { label: "3D board (warm-only)", cmd: "kicad_show_3d_board", args: { filePath: KDEMO + ".kicad_pcb" }, note: "run the KiCad demo first; 3D window is warm-only" }+ ].forEach(function (s) {+ var cc = card("grp-kicad", "verb", s.label);+ cc.btn.onclick = function () { showSurface(cc, { showCmd: s.cmd, showArgs: s.args, stateCmd: "kicad_state", note: s.note }); };+ });+ var cKState = card("grp-kicad", "read", "Show KiCad state (every window)");+ cKState.btn.onclick = function () { showSurface(cKState, { showCmd: "kicad_state", showArgs: { maxWidth: 900 }, stateCmd: "kicad_state", note: "captures every KiCad window as a screenshot" }); };++ // ---- Fusion surfaces: one first-class verb per content type (fusion_show_*) ----+ // Design surfaces need a project open; library surfaces need a library open.+ // (The Fusion tour above stages its own sample content.) On a prerequisite miss+ // the bridge returns a _hint telling you the next move - we surface it.+ [+ { label: "Schematic", cmd: "fusion_show_schematic", args: {}, note: "needs an electronics project open (run the Fusion demo, or fusion_open_cloud_file)" },+ { label: "2D board", cmd: "fusion_show_2d_board", args: {}, note: "needs a project open" },+ { label: "3D board", cmd: "fusion_show_3d_board", args: {}, note: "needs a project open" },+ { label: "Symbol", cmd: "fusion_show_symbol", args: { name: "R" }, note: "needs a library open (fusion_open_lbr), or pass symbolXml" },+ { label: "Footprint", cmd: "fusion_show_footprint", args: { name: "R" }, note: "needs a library open, or pass packageXml" },+ { label: "3D package", cmd: "fusion_show_3d_package", args: { name: "R" }, note: "needs a library open; add stepPath to attach a model" }+ ].forEach(function (s) {+ var cc = card("grp-fusion", "verb", s.label);+ cc.btn.onclick = function () { showSurface(cc, { showCmd: s.cmd, showArgs: s.args, stateCmd: "fusion_state", note: s.note }); };+ }); // Single full-screen shot (keep the simple case too). var cShot = card("grp-do", "verb", "Take a screenshot (whole screen)"); cShot.btn.onclick = function () {