Adom Desktop Demo

An interactive, guided demo of Adom Desktop's features. Claude walks the user through each capability (notifications, screenshots, browser automation, KiCad, Fusion 360, and file transfer) while showing live results in a Hydrogen webview.

REQUIRED: every demo starts with a plan card

Before running any demo steps, push a demo plan manifest card to the webview as the first step. This is a permanent fixture of every Adom demo. No exceptions. Without it the viewer has no idea what they are about to watch and the final video feels disorienting.

Use push_demo_plan <manifest.json> from demo-helpers.sh (see adom-desktop/skills/demo/SKILL.md for the full spec + manifest JSON schema). The manifest MUST include the 11-item preamble checklist (demo server up, webview rendering intro, target app alive, HWNDs exported, sharing on monitor, mic disabled, conflicting tabs removed, helpers sourced in this shell not a subshell, markers initialized, captions ralph-tested, one dry-run push_step cycle verified) and a step-by-step table with speedup + clipped + reasoning columns per step.

render-demo-plan.py generates a branded PNG from the manifest that's embedded in the push_step call. The viewer sees the whole plan before the demo begins.

Architecture

Claude (you — orchestrator)
  │
  ├── Start demo web server (Node.js, port 8790)
  ├── Open Hydrogen Web View tab → /proxy/8790/
  ├── Walk through demo steps, running adom-desktop commands
  └── POST results to http://127.0.0.1:8790/api/step at each step
        → Web UI updates in real-time via SSE

Setup — Run This First

1. Start the demo web server

# Kill any old instance
pkill -f "node.*adom-desktop-demo/server.cjs" 2>/dev/null || true

# Start fresh
node ~/project/adom-desktop/skills/demo/server.cjs &
sleep 1
curl -sf http://127.0.0.1:8790/api/state

2. Open the demo webview in Hydrogen

# Get workspace layout to find a leaf pane ID
LAYOUT=$(adom-cli hydrogen workspace get 2>/dev/null)
# Parse out a leaf pane ID (any leaf will do)
LEAF_ID=$(echo "$LAYOUT" | python3 -c "
import sys, json
layout = json.load(sys.stdin)
def find_leaf(node):
    if node.get('type') == 'leaf':
        return node['id']
    for child in node.get('children', []):
        result = find_leaf(child)
        if result: return result
    return None
print(find_leaf(layout.get('root', layout)))
")

# Add a Web View tab with the demo URL
adom-cli hydrogen workspace add-tab \
  --panel-id "$LEAF_ID" \
  --panel-type "adom/a1b2c3d4-0031-4000-a000-000000000031" \
  --display-name "Desktop Demo" \
  --display-icon "mdi:monitor-cellphone" \
  --initial-state '{"url":"/proxy/8790/"}'

After adding the tab, bring it to the foreground if it isn't already visible. You may need to parse the response for the new tabId and call adom-cli hydrogen workspace active-tab --tab-id <id>.

3. Check desktop connection

adom-desktop ping
  • If connected → proceed to adom-desktop status
  • If not connected → follow the First-Time Setup flow from the adom-desktop skill (steps 1-7). Do NOT duplicate the setup instructions here — defer to that skill. Return here after ping succeeds.

4. Detect installed apps

adom-desktop status

Parse the desktop.apps field:

  • kicad.installed → enable KiCad demo section
  • fusion360.installed → enable Fusion 360 demo section
  • Browser/Puppeteer auto-starts — assume available if Node.js is on the desktop

Push the welcome step to the webview:

curl -s -X POST http://127.0.0.1:8790/api/step \
  -H 'Content-Type: application/json' \
  -d '{"step":0,"total":7,"title":"Welcome","description":"Connected to your desktop! Detected apps: ...","status":"Connected","statusColor":"#3fb950"}'

Demo Steps

For each step: narrate in chat, run the command, push results to the webview, then move to the next step. Use this curl pattern to push updates:

curl -s -X POST http://127.0.0.1:8790/api/step \
  -H 'Content-Type: application/json' \
  -d '<JSON>'

Step 1: Notifications (always runs)

Tell the user: "Let's start simple — I'll send a notification to your desktop from this cloud container."

adom-desktop notify_user '{"title":"Hello from the cloud!","body":"This was sent from your Docker container via Adom Desktop.","level":"info"}'

Push to webview: {"step":1,"total":7,"title":"Desktop Notifications","description":"Sent a notification to your desktop. Check your system tray! I can use these to alert you when builds finish, tests fail, or anything needs your attention.","status":"Sent","statusColor":"#3fb950"}

Step 2: Screenshots (always runs)

Tell the user: "Now let me grab a screenshot of your entire desktop and show it right here."

adom-desktop desktop_screenshot_screen

The command returns a JSON with the screenshot file path (usually /tmp/conduit-screenshots/screen-*.png). Read the file, base64-encode it, and push to the webview:

# Get the screenshot path from the command output (the "savedTo" field)
# Base64-encoded images are too large for command-line curl -d. Write JSON to a file
# and use --data-binary @file:
IMG=$(base64 -w0 /tmp/conduit-screenshots/<filename>.png)
cat > /tmp/step2.json <<EOF
{"step":2,"total":8,"title":"Desktop Screenshots","description":"This is your desktop right now — captured from the cloud.","image":"data:image/png;base64,$IMG","status":"Captured","statusColor":"#3fb950"}
EOF
curl -s -X POST http://127.0.0.1:8790/api/step -H 'Content-Type: application/json' --data-binary @/tmp/step2.json

Gotcha: base64-encoded screenshots routinely exceed the shell arg-list limit (~128 KB). Using -d "..." directly will fail with "Argument list too long". ALWAYS write the JSON payload to a file first.

Then optionally: desktop_list_windows → pick an interesting window → desktop_screenshot_window → push another update with that screenshot.

Step 3: Pup — Browser Automation (runs if available)

Tell the user: "I can also open and control Chrome on your desktop using pup. Watch this."

adom-desktop browser_open_window '{"sessionId":"demo","profile":"demo","url":"https://adom.inc"}'

Wait 3 seconds for the page to load, then:

adom-desktop browser_screenshot '{"sessionId":"demo"}'

Read the screenshot, base64-encode, push to webview. Then:

adom-desktop browser_eval '{"sessionId":"demo","expr":"document.title"}'

Push the eval result as data.

Tell the user: "I opened Chrome, navigated to adom.inc, took a screenshot, and read the page title — all from this cloud container."

Close when done:

adom-desktop browser_close_window '{"sessionId":"demo"}'

Step 4: KiCad (only if kicad.installed)

If KiCad is NOT installed, push a skip message and move on:
{"step":4,"total":7,"title":"KiCad","description":"KiCad is not installed — skipping this section. Install KiCad from kicad.org to see this in action!","status":"Skipped","statusColor":"#d29922"}

If installed:

adom-desktop kicad_install_symbol '{"filePath":"/home/adom/project/gallia/server/samples/ti-bq76952.kicad_sym"}'
adom-desktop kicad_open_symbol_editor

Wait 5 seconds for the editor to open, then verify with window screenshots:

# Discover all KiCad windows and screenshot each one by HWND
EDITORS=$(adom-desktop kicad_window_info 2>&1 | python3 -c "
import sys,json
info = json.loads(json.load(sys.stdin)['output'])['data']
for e in info['editors']:
    print(f\"{e['hwnd']}|{e['title']}\")
")
while IFS='|' read -r HWND TITLE; do
  adom-desktop desktop_screenshot_window "{\"hwnd\":$HWND}"
done <<< "$EDITORS"

Read the screenshots to confirm the symbol is loaded, then push to webview.

Tell the user: "I installed a TI BQ76952 battery monitor symbol into your KiCad libraries and opened the Symbol Editor. For a deeper dive, try /tour."

Close KiCad:

adom-desktop kicad_close '{"force": true}'

Step 5: Fusion 360 (only if fusion360.installed)

If NOT installed, push a skip message.

If installed:

adom-desktop fusion_launch

Wait for it to return (15-30 seconds). Then:

adom-desktop fusion_import_step '{"filePath":"/home/adom/project/gallia/services/wiki/static/3dcomps/3d-soic-8/3d-soic-8.step"}'

Take a desktop screenshot and push to webview.

Tell the user: "I launched Fusion 360 and imported a SOIC-8 3D model."

adom-desktop fusion_close

Step 6: File Transfer (always runs)

Tell the user: "Finally, let me send a file from this cloud container directly to your desktop."

adom-desktop send_files '{"filePaths":["/home/adom/project/gallia/server/samples/ti-bq76952.kicad_sym"],"targetApp":"general","destinationFolder":"adom-demo"}'

Parse the returned destinationPaths and push to webview as data.

Step 7: Wrap-up

Push a final summary step to the webview with all features listed and green status. Send a completion notification:

adom-desktop notify_user '{"title":"Demo complete!","body":"You have seen the full Adom Desktop experience.","level":"info"}'

In chat, suggest next steps:

  • /tour — deep KiCad walkthrough (12 steps)
  • "Open a browser to [url]" — Puppeteer
  • "Send [file] to my desktop" — file transfer
  • "Take a screenshot of my desktop" — screenshots

Cleanup

After the demo ends:

# Kill the demo server
pkill -f "node.*adom-desktop-demo/server.cjs" 2>/dev/null || true

Optionally close the webview tab via adom-cli hydrogen workspace remove-tab.

Pushing updates — API reference

Endpoint Method Description
/api/step POST Push a step update (JSON body)
/api/reset POST Reset state for a fresh run
/api/state GET Get current state
/api/events GET SSE stream
/screenshots/<file> GET Serve screenshot files from /tmp/conduit-screenshots/

Step update JSON fields

Field Type Description
step number Current step number (0-7)
total number Total steps (7)
title string Step title
description string What happened / what the user should see
image string Base64 data URI (data:image/png;base64,...) or URL path
data string Monospace text (command output, eval results, file paths)
dataColor string Color for data text (default: #3fb950 green)
status string Status badge text ("Sent", "Captured", "Skipped", etc.)
statusColor string Status badge color (#3fb950 green, #d29922 yellow, etc.)

Verifying KiCad commands — use kicad_screenshot_all

After ANY KiCad command, use kicad_screenshot_all to capture every KiCad window at once. This is the native command — no manual HWND loop needed:

# 1. Run your KiCad command
adom-desktop kicad_open_schematic '{"filePath":"C:/Users/john/Downloads/demo.kicad_sch"}'
sleep 5

# 2. Screenshot ALL KiCad windows at once (native — uses PID-based detection)
adom-desktop kicad_screenshot_all
# → screenshots: [{type:"editor", hwnd:123, title:"Schematic Editor", savedTo:"C:/tmp/..."},
#                 {type:"dialog", hwnd:456, title:"File Open Warning", savedTo:"C:/tmp/..."}]

# 3. Pull screenshots from Windows to Docker
adom-desktop pull_file '{"filePaths":["C:/tmp/conduit-screenshots/kicad-editor-....webp"],"saveTo":"/tmp"}'

# 4. Read each screenshot to verify:
#    - Did the schematic load? Or is it blank?
#    - Any modal dialogs? (type:"dialog" — shown in red in the grid)
#    - Is the right file open in each editor?

Why kicad_screenshot_all: Uses PID-based window detection — finds ALL KiCad windows regardless of title. Returns screenshots saved on Windows; use pull_file to get them to Docker.

For demo recordings: Build a grid image from the screenshots (Pillow) — editors labeled green, dialogs labeled red. Push the grid to the webview to prove the AI sees every window simultaneously.

Recovering from stuck KiCad dialogs

If a KiCad command fails or times out, a modal dialog may be blocking. Use kicad_window_info to detect:

# 1. Check for modal dialogs
adom-desktop kicad_window_info
# → hasModalDialogs: true, modalDialogs: [{hwnd:456, title:"File Open Warning", ownerHwnd:123}]

# 2. Screenshot the dialog
adom-desktop kicad_screenshot_all  # captures everything including dialogs
# OR: adom-desktop desktop_screenshot_window '{"hwnd": 456}'

# 3. AI reads the screenshot and decides:
#    - "Save changes?" → kicad_close '{"force": true}'
#    - "File already open" → click "Open Anyway" or cancel
#    - "Format upgrade" → usually safe to dismiss, retry
#    - Unknown → tell the user what you see and ask

Recovering from stuck Fusion dialogs

Fusion can show Qt modal dialogs that block the add-in (all commands return "add-in not responding"). The CLI auto-detects this and returns a _hint with recovery steps, but here's the full pattern:

# 1. Command fails with "add-in not responding"
adom-desktop fusion_export_bom
# → error: "Fusion 360 is running but AdomBridge add-in not responding..."
#   _hint: "Fusion add-in is blocked — likely by a modal dialog..."

# 2. Find the blocking dialog
adom-desktop fusion_window_info
# → dialogs: [{hwnd: 7016348, title: "Fusion360", rect: {width: 900, height: 489}}]
# Large dialogs (width > 200) are likely the blocker

# 3. Screenshot the dialog to read it
adom-desktop desktop_screenshot_window '{"hwnd": 7016348}'
# → Shows "Select Electronics Design File" or save prompt or error

# 4. Dismiss based on what you see:
#    - "Select Electronics Design File" → fusion_send_key escape (cancel — file already open)
#      NOTE: Enter does NOT work on this dialog (no row is keyboard-selected).
#      Escape cancels cleanly and the previously opened document stays active.
#    - Save prompt → fusion_send_key escape (don't save)
#    - Error dialog → fusion_send_key enter (dismiss OK)
adom-desktop fusion_send_key '{"key":"enter"}'

# 5. Verify recovery
adom-desktop fusion_get_app_state
# → Should now return success with active document

Common Fusion dialog chains (they cascade — dismiss one and another appears):

  1. open_cloud_file with multiple linked electronics designs → "Select Electronics Design File" (900x489)
    • Escape cancels. To proceed: click the row you want (relative coords x=0.2, y=0.50), then click OK (x=0.88, y=0.93)
  2. After selecting electronics design → "Linked Schematic file has new version(s)" (498x239)
    • Click Yes (x=0.7, y=0.8) to load latest
  3. After loading schematic → "Linked PCB file has new version(s)" (498x239)
    • Click Yes (x=0.7, y=0.8) to load latest
  4. Progress bar may appear briefly — not a real dialog, ignore it

CRITICAL PATTERN: Modal-first loop, not sleep-first.
After ANY Fusion command, check for modals BEFORE sleeping:

for i in 1 2 3 4 5 6 7 8 9 10; do
  sleep 2
  # Check app state — if ok, we're done
  STATE=$(adom-desktop fusion_get_app_state 2>&1 | python3 -c "...")
  if [ "$STATE" = "ok" ]; then break; fi
  # Check for dialogs — handle them immediately
  DHWND=$(adom-desktop fusion_window_info 2>&1 | python3 -c "...")
  if [ -n "$DHWND" ]; then
    # Screenshot → read → click Yes/OK/dismiss
    adom-desktop desktop_screenshot_window "{\"hwnd\":$DHWND}"
    adom-desktop fusion_click_fusion "{\"hwnd\":$DHWND,\"x\":0.7,\"y\":0.8}"
  fi
done

Never just sleep 10 and hope — always poll for state + modals.

Other dialog triggers:

  • Closing modified documents → "Save changes?"
  • Network issues → "Cannot connect to cloud"
  • Add-in crashes → Recovery dialog

Fusion 360 workspace prerequisite

Manufacturing exports, design rules, and EAGLE commands require the Electronics workspace to be active. Check with fusion_get_app_stateisElectronics must be true and activeWorkspace must be PCB Editor (not 3D PCB).

If isElectronics is false:

  • The active document tab is the 3D model view, not the electronics board
  • Switch with fusion_activate_document '{"name":"..."}' or reopen the board file
  • Common trap: fusion_show_3d_board switches to 3D PCB workspace — you need fusion_show_2d_board to get back to Electronics

Fusion 360 design rules (InstaPCB / JLCPCB)

Apply Adom's optimized design rules for JLCPCB manufacturing:

# Show available rules (2-layer and 4-layer with detailed specs)
adom-desktop fusion_set_design_rules '{"action":"show"}'

# Auto-detect layer count and apply matching rules
adom-desktop fusion_set_design_rules '{"action":"apply"}'
# → Applies JLCPCB-optimized track widths, via sizes, clearances, annular rings

# Force 4-layer rules
adom-desktop fusion_set_design_rules '{"action":"apply","layers":"4"}'

Fusion 360 manufacturing exports

After opening a board in Fusion (Electronics workspace active), export manufacturing files for fab:

# Export gerbers (ZIP with all layers — ready for JLCPCB/PCBWay upload)
adom-desktop fusion_export_gerbers
# → zipPath, zipSizeKB, layerCount, files[]

# Export BOM (CSV — component list grouped by value+package)
adom-desktop fusion_export_bom
# → outputPath, componentCount, uniquePartCount

# Export CPL/pick-and-place (CSV — component XY coordinates for assembly)
adom-desktop fusion_export_cpl
# → outputPath, totalPlacements, topCount, bottomCount

# Export board image (PNG with layer presets)
adom-desktop fusion_export_board_image '{"preset":"all"}'
# Presets: all, top_copper, bottom_copper, silkscreen, assembly_top, fabrication, etc.

# Get board data (components, nets, traces, DRC violations)
adom-desktop fusion_board_info

# Pull files back to Docker
adom-desktop pull_file '{"filePaths":["C:/tmp/adom-gerbers/demo_gerbers.zip","C:/tmp/adom-bom.csv","C:/tmp/adom-cpl.csv"],"saveTo":"/tmp/mfg"}'

Window management for recordings

Use desktop_bring_to_front to surface windows during recordings. Pattern: show the app window to the user, pause, then bring the browser back so the screenshot appears in the webview:

# Open an app
adom-desktop kicad_open_schematic '{"filePath":"..."}'
sleep 5

# Show it to the user (window pops to foreground)
adom-desktop desktop_bring_to_front '{"titleContains":"Schematic Editor"}'
sleep 3

# Bring browser back so user sees the webview update
adom-desktop desktop_bring_to_front '{"titleContains":"galliaApril - Editor"}'
sleep 1

# Now push screenshot to webview — user sees the result appear

Shell execute — revoke approvals first for demos

Before demoing shell execute (to show the approval dialog), revoke any prior "Approve All" permissions:

adom-desktop desktop_revoke_approvals
# → revokedCount, autoApproveCleared, deniedPendingApprovals

# Now shell_execute will show the approval dialog fresh
adom-desktop shell_execute '{"command":"echo Hello from Adom Cloud!"}'

Important Notes

  • Don't duplicate setup instructions — defer to the adom-desktop skill for install/connect
  • Clean up after each section — close KiCad, Fusion, browser windows. Leave the desktop as you found it.
  • Use session ID "demo" for Puppeteer to avoid collisions
  • Base64-encode screenshots before pushing to the webview — the image field expects a data URI
  • Narrate in chat AND push to webview — the user should see progress in both places
  • Skip unavailable features gracefully — push a yellow "Skipped" status with a helpful message
  • Path normalization — The CLI auto-converts paths both ways. You can pass C:/Users/john/file.step (forward slashes) and responses always return forward slashes. No need to escape backslashes.
  • kicad_screenshot_all — Use this instead of manual desktop_screenshot_window loops. Native PID-based detection finds all KiCad windows.
  • pull_file — Required to get screenshots/exports from Windows back to Docker. Args: filePaths (array), saveTo (directory).
  • CLI hints — The CLI returns _hint fields on every command response. These are context-aware guidance from the CLI to the AI:
    • KiCad open commands: "WARNING: KiCad may show modal dialogs... ALWAYS check kicad_window_info"
    • KiCad window_info: "To screenshot all windows: kicad_screenshot_all"
    • Fusion exports: "Requires Electronics workspace. Files saved on Windows — use pull_file"
    • Fusion open_cloud_file: "WARNING: May trigger Select Electronics Design File dialog"
    • Fusion set_design_rules: "Requires Electronics workspace. If 'Not an Electron document', switch tabs"
    • Fusion errors ("not responding"): Auto-diagnosis with recovery steps + fusion_window_info
    • Always read _hint — it tells you exactly what to do next and what can go wrong.