← Commit history

Publish 0.1.0

John Lauer ·b047cc09ce ·1mo ago ·parent 162247a
6 files changed +603−5
SKILL.mdadded+98
@@ -0,0 +1,98 @@+---+name: altium-bridge+public: true+user-invocable: true+description: "Drive Altium Designer on the user's Windows PC from this container via the adom-bridge CLI (verbs altium_*). A resident DelphiScript agent inside Altium gives you eval (arbitrary DelphiScript statements), the 1,100-command process launcher (altium_run_command), open/show of every surface (project, schematic, 2D/3D board, library, symbol, footprint, 3D part) with a returned frame, compile/ERC with the Messages panel, DRC with violations, OutJob runs (Gerber, ODB++, NC drill, BOM, pick-and-place), dialog detection/dismissal by PID, and a full-state screenshot array. Trigger words: altium, altium designer, open in altium, show me the board in altium, altium schematic, altium pcb, altium 3d, altium gerber, altium outjob, altium bom, altium drc, altium erc, compile altium project, altium library, altium symbol, altium footprint, run delphiscript, altium_eval, altium_run_command, altium_state, altium demo, altium bridge."+---++# altium-bridge — drive Altium Designer from a container++```bash+adom-bridge --ai-thread "<your thread name>" altium_<verb> '<json args>'+```++Say **altium** to reach this bridge. Every verb returns `success`, a stable `errorCode` on failure, and a `_hint` that tells you the next move — **relay the `_hint` verbatim and branch on `errorCode`, never on prose.**++## FIRST-TIME / COLD-START — read before you panic++Altium is a licensed host app. The bridge never installs it silently; it detects it, launches it, and gets the script engine ready.++| `errorCode` / signal | Meaning | What you do |+|---|---|---|+| `host_app_not_installed` (`hostApp:"Altium Designer"`) | No `C:\Program Files\Altium\AD*\X2.EXE` | Offer to install it, then drive the installer (the `altium-install` skill has the full recipe — the installer elevates, so the last clicks are the user's). |+| `altium_not_running` | Installed, not launched | `altium_launch '{}'` — cold start ≈ 60 s, measured. Altium's own launch is the one legitimate foreground. |+| `not_licensed` (`licenceState:"No License"`/`"Viewer"`) | Running, but scripting is refused without a licence | `altium_license '{}'` drives the trial / On-Demand flow (phase 5) — until then the user picks a licence on Altium's License Management page. |+| `agentAlive:false` in readiness | The resident agent loop is not up | `altium_agent_start '{}'` (≈3 s warm). Every other verb auto-starts it, so you rarely call this. |+| `blocked_by_dialog` + `dialogs[]` | A modal is up on Altium's main window; the script engine and every verb are frozen behind it | Read `dialogs[].shot` (base64 PNG — Altium paints dialog text, it is not exposed as text), then `altium_dismiss_dialogs {hwnd, button}`. A save prompt is NEVER auto-answered. |+| `agent_wedged` / `script_*` dialog | A script error halted the engine (an unknown identifier is a *runtime* error in DelphiScript) | `altium_agent_recover '{}'` — presses OK on the script Error modal, sends `X2 -REditScript:Stop`, clears state. Then retry. |+| `instance_busy` | The X2 helper could not hand its request to the running instance | A modal is open or a script is running: `altium_check_dialogs`, dismiss, retry. |++**Readiness before you drive:** `altium_readiness '{}'` is read-only (installed / running / licensed / agentAlive / ready). For "what EDA tools do I have?" across bridges use ab's `bridge_readiness`.++## The 8 conventions++1. **Poll readiness, don't launch blindly.** `altium_readiness` → act on the row above → drive.+2. **Background by default.** Every `show_*` opens in the background and hands focus back; `windowEtiquette` in the response says where the window went. Only Altium's own cold launch foregrounds. If you must foreground, toast the user first (`notify_user`) and say why.+3. **`ok:true` is not enough — verify.** `show_*` and `state` return frames; look at them. `run_outjob` returns `files[]` actually written.+4. **One script at a time.** While the agent is resident, never run `altium_run_script`; it raises Altium's "Another script executing now" modal. Send work through `altium_eval` / `altium_run_command`.+5. **Never leave a modal.** Prefer commands that carry their parameters (`altium_run_command` with `params`) so no picker opens. After any mutating step, `altium_check_dialogs`.+6. **Reuse the running Altium.** Altium is single-instance; the agent survives across your calls (auto-stops after 30 min idle, auto-restarts on the next verb).+7. **Absolute Windows paths.** `C:/…` or `C:\\…`; no `%VAR%` expansion. Files from the container must be sent first (`send_files`).+8. **Surface `_hint` verbatim + branch on `errorCode`.**++## Core verbs (the 80 % path)++| Verb | Purpose | Key args |+|---|---|---|+| `altium_readiness` | installed / running / licensed / agent state (read-only) | — |+| `altium_state` | base64 screenshot of every Altium window + focused doc + open documents | `screenshots:false` to skip images |+| `altium_show_project` / `_schematic` / `_2d_board` / `_3d_board` | open the design surface in the background, return `shot` | `filePath` (defaults: the shipped Bluetooth Sentinel example) |+| `altium_show_library` / `_symbol` / `_footprint` / `_3d_chip` | open a .SchLib/.PcbLib, select a component, return `shot` | `libraryPath`, `symbolName` / `footprintName` |+| `altium_list_library` | component names in a library | `libraryPath` |+| `altium_open_document` | open any document/project by path (background) | `filePath` |+| `altium_eval` | run DelphiScript **statements** (`Begin … End;`) inside Altium | `text` |+| `altium_run_command` | run any of Altium's 1,111 `Server:Command` processes | `command:"PCB:Zoom"`, `params:"Action=All"` |+| `altium_list_commands` | search the process catalog (parsed from Altium's own .rcs tables) | `query`, `server`, `limit` |+| `altium_compile` (`altium_run_erc`) | compile the project, return the Messages panel | `projectPath` |+| `altium_run_drc` | run the PCB Design Rule Check, return `violations[]` | `filePath` |+| `altium_run_outjob` | run an OutJob medium; lists `files[]` written | `outjobPath`, `medium:"Folder Structure"`, `projectPath` |+| `altium_export_gerber` / `altium_export_bom` | Fabrication / Assembly OutJob shortcuts | `outjobPath` |+| `altium_check_dialogs` / `altium_dismiss_dialogs` | dialogs by PID with body shot; press a button in the background | `hwnd`, `button` |+| `altium_create_footprint` / `altium_create_symbol` | author a footprint (pads) / symbol (pins) into a .PcbLib/.SchLib, creating the library if missing | `name`, `libraryPath`, `pads` / `pins` |+| `altium_set_params` | parameters (LCSC Part #, MPN, …), comment, footprint link on a symbol | `libraryPath`, `symbolName`, `params`, `footprint` |+| `altium_add_body` | 3D body on a footprint — STEP model or extruded box | `libraryPath`, `footprintName`, `step` \| `bwMil,bhMil,hMil` |+| `altium_install_library` / `altium_compile_intlib` / `altium_extract_intlib` | Installed Libraries, .LibPkg → .IntLib, .IntLib → sources | `libraryPath` / `libpkgPath` / `intlibPath` |+| `altium_save` | save the focused document | `filePath` |+| `altium_signin_status` / `altium_license` / `altium_connect_workspace` | account + licence + workspace state; drive the License Management page (`action: show\|refresh\|use_license\|start_trial`) | — |+| `altium_a365_configure` / `_status` / `_query` | Altium 365 GraphQL (needs a token from a workspace that has Admin → Developer; the Develop trial does not) | `workspace`, `token`, `query` |+| `altium_demo` | six-beat tour: symbol → footprint → 3D part → schematic → 2D → 3D board | `stage` |+| `altium_describe` | the full machine-readable catalog | — |++Lifecycle: `altium_launch`, `altium_close`, `altium_agent_start` / `_stop` / `_status` / `_recover`, `altium_diagnostics`, `altium_progress`, `altium_uninstall`.++## Quick examples++```bash+adom-bridge --ai-thread "board review" altium_readiness '{}'+adom-bridge --ai-thread "board review" altium_show_3d_board '{"filePath":"C:/designs/foo/foo.PcbDoc"}'+adom-bridge --ai-thread "board review" altium_run_command '{"command":"PCB:Zoom","params":"Action=All"}'+adom-bridge --ai-thread "board review" altium_eval '{"text":"Begin PCBServer.GetCurrentPCBBoard.ViewManager_FullUpdate; End;"}'+adom-bridge --ai-thread "board review" altium_compile '{"projectPath":"C:/designs/foo/foo.PrjPcb"}'+adom-bridge --ai-thread "board review" altium_export_gerber '{"outjobPath":"C:/designs/foo/Fabrication.OutJob","projectPath":"C:/designs/foo/foo.PrjPcb"}'+```++## What is NOT yet real++- `altium_export_step` / `altium_import_kicad` / `altium_install_extension` ship Barrett's AdomBridge .NET extension, but on AD 26.9.1 the server never loads into the running instance (measured; see the plan skill). They fail loudly with `extension_timeout` until it is rebuilt for AD 26. Headless STEP today: an OutJob with an ExportSTEP output needs one human click on *Generate content*.+- `altium_a365_*` need a token; the Develop trial workspace has no Admin → Developer page.++## DelphiScript facts you need for `altium_eval`++- Statements only — `Begin … End;` — no `Var` block (use `altium_run_script` with a `.PrjScr` for real units, and only when the agent is stopped).+- Single quotes for strings; no double quotes anywhere in the text.+- Unknown identifiers fail at **runtime** with an `Error` modal and a halted engine → `altium_agent_recover`.+- To get data OUT, write a file from the script and read it back; `altium_run_command` / the structured verbs already do this for the common cases.++## How this bridge is packaged++Wiki page `adom/altium-bridge`: the Release zip + `adom-bridge-altium-manifest.json` (what Adom Bridge installs with `bridge_install`), and this skills pkg (`adom-wiki pkg install adom/altium-bridge` — skills only; it does not install the runtime). Plan of record and every measured engine fact: the `altium-bridge-plan` skill.
install.shadded+12
@@ -0,0 +1,12 @@+#!/usr/bin/env bash+# Deploy the altium-bridge USER skills into every agent home this container has.+set -euo pipefail+HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"+install_skill() {  # <dest-dir> <src-file>+  mkdir -p "$1" && cp -f "$2" "$1/SKILL.md"+}+for HOME_DIR in "$HOME/.claude/skills" "$HOME/.codex/skills"; do+  install_skill "$HOME_DIR/altium-bridge"       "$HERE/SKILL.md"+  install_skill "$HOME_DIR/altium-bridge-plan"  "$HERE/skills/altium-bridge-plan/SKILL.md"+done+echo "OK: altium-bridge skills installed (altium-bridge, altium-bridge-plan)"
package.jsonadded+90
@@ -0,0 +1,90 @@+{+  "slug": "altium-bridge",+  "version": "0.1.0",+  "type": "app",+  "description": "Skills for your container so your AI knows how to drive the Altium Designer bridge (altium_* verbs). The bridge runtime itself is the release zip; Adom Bridge loads that.",+  "tags": [+    "altium",+    "altium-designer",+    "pcb",+    "schematic",+    "eda",+    "bridge",+    "adom-bridge",+    "desktop-app"+  ],+  "dependencies": {+    "adom/adom-bridge": "^2.0.3"+  },+  "scripts": {+    "install": "./install.sh",+    "uninstall": "./uninstall.sh"+  },+  "files": [+    "README.md",+    "SKILL.md",+    "package.json",+    "install.sh",+    "uninstall.sh",+    "skills/altium-bridge-plan/SKILL.md"+  ],+  "discovery_triggers": [+    "open my design in altium",+    "show me the board in altium",+    "open altium",+    "launch altium designer",+    "altium schematic",+    "altium pcb",+    "show the pcb in 3d in altium",+    "altium 3d board",+    "run drc in altium",+    "altium design rule check",+    "compile my altium project",+    "altium erc",+    "generate gerbers from altium",+    "altium gerber",+    "altium outjob",+    "run the output job",+    "altium bom",+    "export bom from altium",+    "altium pick and place",+    "altium odb++",+    "open an altium library",+    "altium symbol",+    "altium footprint",+    "altium schlib",+    "altium pcblib",+    "run a delphiscript in altium",+    "altium script",+    "altium eval",+    "altium process launcher",+    "control altium designer",+    "drive altium",+    "automate altium",+    "altium bridge",+    "altium demo",+    "altium screenshot",+    "what is open in altium",+    "altium dialog",+    "altium not responding"+  ],+  "discovery_pitch": "Drive the user's real Altium Designer from an AI: open any design surface, author native libraries with 3D bodies, run DRC and OutJobs, and screenshot everything back.",+  "sample_prompts": [+    {+      "label": "Show my board in 3D",+      "prompt": "open my PCB in altium and show me the board in 3D"+    },+    {+      "label": "Import a part",+      "prompt": "add the CH340G from the wiki to my altium library with symbol, footprint and 3D body"+    },+    {+      "label": "Run DRC",+      "prompt": "run a design rule check on my altium board and show me the violations"+    },+    {+      "label": "Export Gerbers",+      "prompt": "generate gerbers and a BOM from my altium project"+    }+  ]+}\ No newline at end of file
page.json+86−5
@@ -13,14 +13,79 @@     "name": "John Lauer",     "email": "[email protected]"   },-  "tags": [],+  "tags": [+    "altium",+    "altium-designer",+    "pcb",+    "schematic",+    "eda",+    "bridge",+    "adom-bridge",+    "desktop-app"+  ],   "license": "proprietary",   "visibility": {     "public": true   },-  "sample_prompts": [],-  "discovery_triggers": [],-  "discovery_pitch": null,+  "sample_prompts": [+    {+      "label": "Show my board in 3D",+      "prompt": "open my PCB in altium and show me the board in 3D"+    },+    {+      "label": "Import a part",+      "prompt": "add the CH340G from the wiki to my altium library with symbol, footprint and 3D body"+    },+    {+      "label": "Run DRC",+      "prompt": "run a design rule check on my altium board and show me the violations"+    },+    {+      "label": "Export Gerbers",+      "prompt": "generate gerbers and a BOM from my altium project"+    }+  ],+  "discovery_triggers": [+    "open my design in altium",+    "show me the board in altium",+    "open altium",+    "launch altium designer",+    "altium schematic",+    "altium pcb",+    "show the pcb in 3d in altium",+    "altium 3d board",+    "run drc in altium",+    "altium design rule check",+    "compile my altium project",+    "altium erc",+    "generate gerbers from altium",+    "altium gerber",+    "altium outjob",+    "run the output job",+    "altium bom",+    "export bom from altium",+    "altium pick and place",+    "altium odb++",+    "open an altium library",+    "altium symbol",+    "altium footprint",+    "altium schlib",+    "altium pcblib",+    "run a delphiscript in altium",+    "altium script",+    "altium eval",+    "altium process launcher",+    "control altium designer",+    "drive altium",+    "automate altium",+    "altium bridge",+    "altium demo",+    "altium screenshot",+    "what is open in altium",+    "altium dialog",+    "altium not responding"+  ],+  "discovery_pitch": "Drive the user's real Altium Designer from an AI: open any design surface, author native libraries with 3D bodies, run DRC and OutJobs, and screenshot everything back.",   "metadata": {},   "created_at": "2026-08-30T13:43:48.604Z",   "updated_at": "2026-08-30T13:43:48.604Z",@@ -31,5 +96,21 @@     "version_cmd": ""   },   "skills": [],-  "org": "adom"+  "org": "adom",+  "description": "Skills for your container so your AI knows how to drive the Altium Designer bridge (altium_* verbs). The bridge runtime itself is the release zip; Adom Bridge loads that.",+  "dependencies": {+    "adom/adom-bridge": "^2.0.3"+  },+  "scripts": {+    "install": "./install.sh",+    "uninstall": "./uninstall.sh"+  },+  "files": [+    "README.md",+    "SKILL.md",+    "package.json",+    "install.sh",+    "uninstall.sh",+    "skills/altium-bridge-plan/SKILL.md"+  ] }\ No newline at end of file
skills/altium-bridge-plan/SKILL.mdadded+311
@@ -0,0 +1,311 @@+---+name: altium-bridge-plan+user-invocable: false+description: >-+  THE plan of record for the Altium Designer bridge (keyword `altium`, verbs `altium_*`) —+  every automation surface Altium exposes, measured on a real AD 26.9.1 install, ranked into+  lanes: the ScriptingSystem:RunScriptText eval, the resident DelphiScript agent (the REPL lane),+  the 1,109-command process-launcher catalog, X2.EXE -R command-line bootstrap, UIA over the+  VCL/DevExpress shell (and CDP for its WebView2 pages), the Altium 365 GraphQL API + embeddable+  viewer, file-format parsers, the native SDK extension, and the sign-in/license gates. Includes+  the verb catalog, the dialog doctrine, the phase plan with acceptance criteria, and the Phase 0+  experiments still owed. Refer back here before any altium-bridge work. Trigger words — altium+  bridge, altium_ verbs, drive altium, control altium designer, altium automation, altium+  delphiscript, RunScriptText, altium process launcher, altium 365 api, altium sign in license,+  altium add-in, altium plan.+---++Parent skill: **adom-bridge-sdk-new-app** (the method) and **adom-bridge-sdk** (the contract).+Written 2026-08-29 from live probes of Altium Designer 26.9.1 on John's box (`galliaApril`+container → `AdomLapper` desktop) plus the vendor docs and the community MCP bridges.++# altium-bridge — plan of record++## 0. What we measured on the box (evidence, not guesses)++| Fact | Evidence |+|---|---|+| Install | `C:\Program Files\Altium\AD26\X2.EXE`, 26.9.1 (Build 10). `System\` holds 634 DLLs, 47 `.rcs` process tables, 71 `.ins` server descriptors. No `Examples\Scripts` shipped. |+| Process | `X2.exe` pid 54932, main window class **`TDocumentForm`**, title `License Management - Altium Designer (26.9.1) - No License`. |+| Script engine | `ScriptingSystem.dll` + `ScriptingSystem.ins` declaring commands **`RunScript`, `RunScriptFile`, `RunScriptText`**; `EditScript.dll/.rcs` (script editor); `Altium.EditScript.Interfaces.dll`; `OUT_Script.dll`. Prefs page *Scripting System → Global Projects / Form Designer*. |+| Command catalog | **3,205 process launchers → 1,109 distinct `Server:Command` strings** in the `.rcs` files: CAMTASTIC 230, PCB 211, Sch 190, TextEdit 100, WorkspaceManager 87, Client 50, SimView 44, IntegratedLibrary 28, UnifiedComponent 26, VersionControl 23, PCB3D 13, … Format: `PL <id> Command='PCB:Zoom' Params='Action=...\|...' Shortcut1='...' End`. |+| Listening ports | **none** while idle. No application COM automation server registered — only document ProgIDs (`AltiumLibrary`, `AltiumDraftsmanDocument`, …). |+| UIA | The VCL/DevExpress shell **IS exposed**: 45 nodes on the licence screen — `MainMenu` MenuItems (File/View/Project/Window/Help/Preferences…), `TGUIDocBarButton`s, `TdxTabSheet` panes, all with `automationId`s and `invokable:true`. The **Home / License Management page content is a WebView2** (`ProgramData\...\WebView2Cache` exists) — its *Start Free Trial / Get License / Sign Out* buttons are **not** in the UIA tree. |+| State dirs | `%APPDATA%\Altium\Altium Designer {ED62C102-…}\` (prefs), `%APPDATA%\Altium\AD26\`, `C:\ProgramData\Altium\Altium Designer {ED62C102-…}\Extensions\` (Altimade, Draftsman, IPC Footprint Generator, EXPORT3D, IPC2581, SchLibGenerationTool, …), `HKCU\Software\Altium\Altium Designer {ED62C102-…}`. |+| Auth state | Signed in as *John John* (Altium account), **no license**; a free *Altium Designer Viewer* seat is offered and a **15-day trial** button exists. Vendor docs: **Viewer mode prohibits scripting** → licence is a hard prerequisite for every in-app lane. |++## 1. The surfaces, ranked into lanes++### L2 — the REPL lane: `ScriptingSystem:RunScriptText` + a resident DelphiScript agent ⭐++Altium has a literal eval: process **`ScriptingSystem:RunScriptText`** with parameter+**`Text=Begin … End;`** (vendor docs, and the string is in `ScriptingSystem.dll`). Plus+`RunScript(ProjectName=…|ProcName=…)` and `RunScriptFile`. DelphiScript reaches the full DXP API+(`PCBServer`, `SchServer`, `GetWorkspace`, `Client`, `RunProcess`), so this is Fusion's+`run_modeling_script` equivalent.++Design (the same shape the two best community bridges converged on — `salitronic/eda-agent`+~400 tools, `coffeenmusic/altium-mcp`):++1. Ship `Altium_Agent.PrjScr` (+ `json_utils.pas`, `dispatcher.pas`) in the Release zip; install+   it as a **Global Project** (Scripting System prefs) so it survives restarts.+2. Bootstrap from the bridge with the CLI lane: `X2.EXE -RScriptingSystem:RunScript(ProjectName="…\Altium_Agent.PrjScr"^|ProcName="StartAgent")`.+3. `StartAgent` runs a **polling loop on the UI thread** with `Application.ProcessMessages`+   yields (eda-agent measured ~10 ms/call active, 100 ms idle polling; auto-stop after 10 min idle+   unless the bridge keep-alives every 30 s). Transport: **file drop** `request_<id>.json` →+   `response_<id>.json` under the bridge cache (works in any DelphiScript); **Phase 0 experiment**:+   `CreateOleObject('MSXML2.ServerXMLHTTP')` for a direct HTTP long-poll to the bridge.+4. `altium_eval {text}` = the agent runs `RunProcess('ScriptingSystem:RunScriptText', 'Text=…')`+   (or writes a temp `.pas` and `RunScriptFile`) and returns stdout captured to a file.+   Everything else — `get_state`, `place_component`, `run_drc`, `create_footprint` — is a canned+   eval with rich hints.++Known engine traps (all from the community bridges, all to design around, not discover again):+- **DelphiScript is untyped**: no records/classes/pointers; single-threaded; a runtime error+  ("Undeclared identifier", "Could not convert variant of type (Dispatch)") **pauses the debugger+  and every later run silently no-ops** until it is stopped. The bridge must detect this+  (heartbeat file stops updating) and recover it (UIA-click *Stop* in the Script IDE, or+  `Client:CloseDocument` the script; then re-bootstrap).+- Characters above **U+00FF become `?`** on the way in — ASCII-escape JSON.+- While the loop is resident, Altium's **own** script-driven buttons are unresponsive; expose+  `altium_agent_detach`.+- No scripted ECO executor: `proj_sync_pcb` runs the GUI ECO and blocks on its dialog → L4.++### L3 — the process-launcher lane (1,109 commands, no UI, no focus)++`RunProcess('PCB:DesignRuleCheck')`, `RunProcess('WorkspaceManager:Compile')`,+`Client:OpenDocument`, `Client:CloseDocument`, `PCB:SwitchTo3D`, `PCB:Zoom Action=All`,+`Sch:AddComponentPart`, `WorkspaceManager:GenerateReport ObjectKind=OutputBatch|Action=Run`+(the OutJob runner from Altium's own CI article). `altium_run_command {command, params}` exposes+the whole table; `altium_describe` ships the catalog parsed from the `.rcs` files on the box+(`coffeenmusic/altium-mcp` keeps one as `dev/altium_process_catalog.txt`). Deterministic and+background — prefer it over any menu click.++### L6 — the CLI lane (bootstrap + batch)++`X2.EXE "<doc>"` opens documents; `-R<Server>:<Command>(Param="v"^|Param2="v")` remote-executes+a process. Altium's CI recipe: run a script that opens `Build.OutJob`, runs the OutputBatch, then+`TerminateWithExitCode(0)`. **Phase 0 experiment:** confirm `-R…` targets the *running* instance+(single-instance forward) vs spawning a second `X2.exe`, and whether `-RScriptingSystem:RunScriptText(Text="…")`+works straight from the shell (that would make bootstrap one line).++### L4 — UIA over the VCL shell; CDP for the WebView2 pages++`desktop_ui_tree` sees menus, doc-bar buttons, panels, status bar → `desktop_ui_click` /+`desktop_ui_set` for Preferences (incl. *Scripting System → Global Projects* install), dialogs,+the Projects/Components panels. The Home/License pages are **WebView2**: launch X2 with+`WEBVIEW2_ADDITIONAL_BROWSER_ARGUMENTS=--remote-debugging-port=<n>` (Phase 0 experiment) and drive+them with CDP (`nbrowser`/pup-style), else image-space clicks with `coordMap.shotId`.+Dialog handling per `fusion-driving`: scan every window owned by the X2 PID, classify by title,+screenshot each, pre-empt what can be pre-empted (save first; pass every parameter; `Client:CloseDocument`+with explicit params), and treat any sweep as a logged defect.++### L5 — Win32 lane++`PostMessage(WM_COMMAND)` after a `GetMenu` walk for cold-start actions before the agent binds;+`PostMessage` keys to exact hwnds; **never** `SendInput`/`SetCursorPos` on a default path. Altium+does not run elevated, so ab's synthetic input reaches it — but it is still the last rung.++### L1 — Altium 365 cloud lane (works with the app closed)++GraphQL at `https://{workspace}.altium.com/api/graphql/` (Voyager at `/api/voyager`, Nitro IDE),+`Authorization: Bearer <token>`; tokens from **Admin → Developer** (1-year access tokens, or+refresh + client credentials). Bounded contexts: **Design** (projects, variants, releases,+manufacturing packages), **Library** (components, symbols, footprints, part requests),+**Procurement** (BOMs), **Platform**, **Collaboration** (comments, tasks), **Customization**+(**workflows, scripts, script executions** — cloud-run scripts). Reads *and writes*. Plus the+**embeddable viewer** (`viewer.altium.com/client/static/js/embed.js`, `data-project-src=<zip url>`+or `data-project-token`) renders schematic/PCB/3D/BOM from a design zip — a render lane that+needs no desktop. Verbs: `altium_a365_search/browse/project/bom/component/release/run_script`,+`altium_web_preview`. Unknowns to verify: plan gating for tokens on a trial workspace; rate limits.++### L0 — file lane (no app, no licence)++`.PcbDoc/.SchDoc/.PcbLib/.SchLib` are OLE-CFB containers; ASCII PcbDoc exists. Parsers:+KiCad's Altium importer (best maintained; `dev-docs.kicad.org/en/import-formats/altium/`),+`vadmium/python-altium` (schematic), `thesourcerer8/altium2kicad`, `AtoK`, `PyAltium` (alpha),+`embedded-society/altium-designer-mcp` (writes PcbLib/SchLib primitives). Use for: `altium_state`+evidence when the app is closed, lint, library generation (IPC-7351 from the Adom molecule+pipeline), and KiCad-side previews.++### L7 — the native SDK extension (Phase 5, optional)++Altium Developer extension → SDK at `C:\ProgramData\Altium\Altium Designer {…}\Extensions\Altium Developer\SDK`;+DLL + `.ins` + `.rcs`; Delphi XE2+/VS2010+ C++/**C#**. A C# server DLL hosting an in-process HTTP+listener would be the Fusion-grade add-in (no file polling, real threads marshalled to the UI).+Costs a licensed AD + a build toolchain; do it only after L2 proves insufficient.++## 2. Authentication & licensing — the gates, in order++1. **Altium account sign-in** — system browser OAuth (email / Google / SSO). Drive with+   `nbrowser`/pup from the bridge; the user's Google session on this box is warm. Human step: password/2FA only.+2. **Licence** — On-Demand / Standalone / **Trial (15 days)** / **Viewer (no scripting)**. The+   trial button lives in the WebView2 licence page → CDP or image click, one toast to the user+   because it is a contractual click. `altium_readiness` must report `licensed:false` with the+   exact next verb, and refuse in-app lanes while unlicensed (they fail silently otherwise).+3. **Altium 365 API token** — Admin → Developer in the workspace web UI; drive it, store the token+   under `%LOCALAPPDATA%\Adom Bridge\bridges-cache\altium\a365.json`, never in the repo.+4. **Scripting enablement** — verify whether AD 26 gates DelphiScript behind a preference+   (`Legacy.Scripts.SupportOldLanguages` exists for VBScript/JScript; check for a DelphiScript+   gate) and whether a Global Project can be registered by writing the prefs file directly.++## 3. Verb catalog (v1 target)++| Group | Verbs |+|---|---|+| Contract | `altium_readiness` `altium_status` `altium_describe` `altium_state` `altium_progress` `altium_uninstall` `altium_diagnostics` |+| Lifecycle | `altium_launch` `altium_close` `altium_signin` `altium_license {trial\|viewer\|ondemand}` `altium_agent_start/stop/detach` |+| REPL | `altium_eval {text}` `altium_run_command {command, params}` `altium_run_script {prjscr, proc}` |+| Documents | `altium_open_project` `altium_open_document` `altium_close_document` `altium_save` `altium_compile` |+| Eight surfaces | `altium_show_symbol` `_footprint` `_3d_chip` `_library` `_schematic` `_2d_board` `_3d_board` `_project` |+| Evidence | `altium_screenshot_all` `altium_window_info` `altium_check_dialogs` `altium_dismiss_dialogs` |+| Outputs | `altium_run_outjob` `altium_export_gerber/odb/ipc2581/step/pdf/bom` `altium_run_drc` `altium_run_erc` |+| Libraries | `altium_create_symbol` `altium_create_footprint` `altium_import_library` `altium_list_library` `altium_install_library_bundle` |+| Cloud | `altium_a365_*` `altium_web_preview` |+| Demo | `altium_demo` (symbol → footprint → 3D part → schematic → 2D → 3D board, one beat per call, same shape as `kicad_demo`/`fusion_demo`) |++Every verb: `_hint`/`_next`, stable `errorCode` (`host_app_not_installed`, `not_licensed`,+`agent_not_running`, `agent_wedged`, `foreground_reason_required`, …), `windowEtiquette`,+`dialogsDetected`/`dialogs[]`, measured `progress` on anything > 3 s, caller identity forwarded on+every ab callback with `X-Adom-Caller-Delegate: altium`.++## 4. bridge.json essentials++```jsonc+{ "name":"altium", "displayName":"Altium Designer", "spawn":{"kind":"python","entrypoint":"server.py","port":0,"persistent":true},+  "verbPrefixes":["altium_"], "statusVerb":"altium_status", "uninstall":"altium_uninstall",+  "detect":{"hostApp":"Altium Designer","appPathsExe":"X2.EXE","paths":{"windows":["%ProgramFiles%\\Altium\\AD*\\X2.EXE"]}},+  "timeouts":{"default":60,"verbs":{"launch":240,"run_outjob":600,"export_step":360,"demo":300}},+  "docs":"https://wiki.adom.inc/adom/altium-bridge" }+```+Python binds `ADOM_BIND_HOST`; scratch under `%LOCALAPPDATA%\Adom Bridge\bridges-cache\altium\` with+the kicad retention rule (48 h / 300 files / oldest-first / 10-min rate limit).++## 5. Dialog doctrine for Altium (pre-empt, never sweep)++Commands that prompt → the pre-emption: closing a dirty doc → `WorkspaceManager:SaveObject` first or+`Client:CloseDocument` with `SaveMode`; ECO → run compile first and drive the ECO dialog via UIA+with a classified catalog; OutJob overwrite → pre-delete outputs; "Confirm" style boxes from+DelphiScript (`ShowMessage`) → never call them in the agent; Script IDE debugger halt → detect by+heartbeat and recover. Keep `handlers/dialog_classify.py` with title → category → resolution.++## 6. Phases + acceptance++0. **Phase 0 experiments (owed, on a LICENSED AD; ~1 day):** (a) trial licence via CDP/image click+   with toast; (b) `-R` forwards to running instance?; (c) `RunScriptText` from CLI?; (d) DelphiScript+   `CreateOleObject('MSXML2.ServerXMLHTTP')`?; (e) ProcessMessages loop keeps UI responsive?; (f)+   WebView2 `--remote-debugging-port` honoured?; (g) Global Project registration by prefs-file+   write?; (h) runtime-error recovery path; (i) ASCII PcbDoc export via `PCB:Export`; (j) A365 token+   on a trial workspace.+1. **Skeleton** (1–2 days): bridge.json, server, readiness/status/describe/state/uninstall/diagnostics,+   screenshot array by PID, rcs catalog parser. *Green chip on a fresh VM; `state` returns real shots.*+2. **Bootstrap + REPL** (3–5 days): agent PrjScr, Global Project install, CLI bootstrap, file-drop+   transport, `eval`, `run_command`, heartbeat + wedge recovery. *100 sequential evals, p50 recorded.*+3. **Eight surfaces + demo** (3–5 days): each `show_*` background + frame; `altium_demo`. *Web-control+   page renders all eight.*+4. **Outputs + libraries** (5 days): OutJob runner, exports, DRC/ERC, symbol/footprint creation,+   library bundle install from the wiki, dialog catalog. *Adom molecule round-trip: wiki part →+   Altium library → board → Gerber.*+5. **Cloud + auth** (3 days): sign-in + licence driven with one toast, A365 verbs, web preview.+6. **Publish**: Release zip + manifest, skills pkg (`altium-bridge` core + `altium-driving`,+   `altium-libraries`, `altium-a365`, `altium-signin` sub-skills), 30–40 discovery triggers, hero,+   `make release`, audit checklist. Then (optional) Phase 5: C# SDK extension.++## 6b. Phase 0 RESULTS (measured 2026-08-29, AD 26.9.1 Trial, AdomLapper) — the facts the code is built on++| Experiment | Result |+|---|---|+| Trial licence | `Start Free Trial` (WebView2, image click) → Edge opens `altium.com/365-intro/activate-add-eval` → workspace form (name, region, terms, **Activate**) → workspace `Adom` provisioned at `john-john-3.365.altium.com` in ~60 s → back in AD: **Refresh**, then **Use License** on "Altium Designer Develop · On-Demand · Trial · 30 days". Title flips to `… - Trial`. |+| `-R` forwarding | A second `X2.EXE -R<Server>:<Cmd>(…)` forwards to the running instance and exits in ~2–8 s. **If the instance is busy (any modal, or a script running) the helper hangs and shows its own "There is another instance … busy" Information dialog** — kill it. |+| `RunScriptText` | Works from the shell: `-RScriptingSystem:RunScriptText(Text="Begin RunApplication('…'); End;")`. **Statements only** — a `Var` block is "Unknown identifier: Var". |+| `RunScript` | `-RScriptingSystem:RunScript(ProjectName="…\X.PrjScr"\|ProcName="Main")` works with a plain `\|` when the arg is passed VERBATIM (PowerShell `Start-Process`, or Python `Popen("<one string>")`). `Popen([exe, arg])` re-quotes (the path has a space) and the helper never forwards. `^\|` is only for cmd.exe. |+| Concurrency | **One script at a time.** A -R script while the agent loop is resident raises `Error: Another script executing now` on the MAIN window; every later -R is refused until it is dismissed. |+| Late binding | An unknown identifier (`CreateOleObject`, `RunProcessWithParameters`) is a **runtime** error: `Error: Undeclared identifier …` modal + the Script editor opens with the **debugger halted** on that line. The halted script counts as executing. |+| Recovery | Press OK on the Error (background `BM_CLICK`), then `X2.EXE -REditScript:Stop` (helper exits ≈1 s), then scripts run again. Implemented as `altium_agent_recover`. |+| `CreateOleObject` | **Not available** → no HTTP from inside. Transport is file drop. |+| `FindFirst('request_*.txt')` | Never matched from the engine. Use ONE fixed inbox `request.txt` (atomic tmp+rename, serialized by the bridge). |+| `RunProcess` | `ResetParameters; AddStringParameter(k,v)…; RunProcess('Client:ArrangeAllWindows')` works from the loop: 828 ms in-agent. |+| Agent loop | `Application.ProcessMessages` + `Sleep(10)` (100 ms idle) keeps AD responsive; heartbeat `hb.txt` every 500 ms; **start → heartbeat 1.5 s**; `eval` round trip 0.7 s; `run_command` 1.4 s. RunScript opens the `.PrjScr` as a document in the Projects panel (harmless). |+| ab spawn | Port 47311 is in a Hyper-V reserved range (`WinError 10013`) — always `--port 0`/ab-assigned. `shell_execute` does not return while a process it started (the server) is alive — start long-lived processes via ab's spawn/`launch_process`, never a foreground shell. |++The bridge source lives in `~/project/altium-bridge/src` (server.py, handlers/agent.py, handlers/dialogs.py, handlers/rcs_catalog.py, handlers/altium_detect.py, agent/AdomAgent.pas.tmpl).++## 6c. Phase 1–3 RESULTS (2026-08-29 evening) — what is DONE++- ab adopted the bridge from `bridges-cache\altium` after `refresh_bridges {name:"altium"}`; it spawns on stable port 64070; `adom-bridge altium_*` works from the container. `bridge_kill {name,confirm:true,reason}` reloads code.+- **All eight surfaces return real frames** (base64 PNG of the main window, background, foreground restored): show_project 6.7 s, show_schematic 6.8 s, show_2d_board 8.7 s, show_3d_board 14.5 s, show_library 21.6 s (39 components listed), show_symbol 18.4 s, show_footprint 5.1 s warm (a cold PcbLib open blew ab's 60 s default — declare `timeouts` and expect it), show_3d_chip 16.4 s. `altium_demo {stage}` chains them.+- Agent lane: eval 0.7–1.1 s, run_command ~1.4 s, `lib_list` / `lib_select` via `SchServer.GetCurrentSchDocument` + `SchLibIterator_Create` / `PCBServer.GetCurrentPCBLibrary.GetComponent(i)` + `CurrentComponent`.+- Opening docs: `Client.OpenDocument('', path)` returns Nil on AD 26 — use `ResetParameters; AddStringParameter('ObjectKind','Document'|'Project'); AddStringParameter('FileName',path); RunProcess('WorkspaceManager:OpenObject')`, then `Client.GetDocumentByPath` + `Client.ShowDocument`.+- Three real-world races found and fixed: (1) a busy loop (long RunProcess) has no heartbeat → never auto-start a second loop (`alive` = hb < 60 s, `responsive` = < 3 s); (2) a killed -R helper's request is still DELIVERED later by Altium → reap stray X2 helpers before every start; (3) `tasklist` took 7 s under load → Toolhelp32 snapshot in-process.+- Dialog text is PAINTED (no Static child, not in UIA) — `altium_check_dialogs` attaches a base64 shot per dialog; OK buttons are `TXPBitBtn` (BM_CLICK) with ab `desktop_ui_click` as fallback.++## 6d. Phase 4 RESULTS (2026-08-29 night) — outputs lane++- `altium_compile` / `altium_run_erc`: `WorkspaceManager:Compile Action=Compile|ObjectKind=Project`, then read the Messages panel via `GetWorkspace.DM_MessagesManager` → **`MessagesCount`, `Messages(i).MsgClass/Source/Document/Text`** (`DM_MessageCount` does NOT exist). 1.7 s. Declare the manager as `IDMMessagesManager` (that type name compiles); `IMessagesManager`/`IMessageItem` as Var types give "Error in declaration block".+- `altium_run_outjob {outjobPath, medium}` → `WorkspaceManager:OpenObject` the .OutJob, then `WorkspaceManager:GenerateReport Action=Run|ObjectKind=OutputBatch|OutputMedium=<name>|DisableDialog=True`. Fabrication.OutJob → Gerber + NC Drill + ODB++ + DRC report in 23.6 s; Assembly.OutJob → BOM .xlsx + pick-and-place in 16.6 s. Output lands in `Project Outputs for <project>` next to the project. `altium_export_gerber` / `altium_export_bom` are those two mediums.+- `altium_run_drc`: `PCB:DesignRuleCheck` opens a MODAL "Design Rule Checker [mm]" (automationId `Run_Button` = "Run Design Rule Check...") — the agent blocks inside RunProcess, the bridge clicks the button through ab UIA in the background, then reads violations off the board (`BoardIterator` filtered `eViolationObject`, `V.Rule.Name`, `V.Description`).+- **Script-engine gotchas that cost the most time:** (1) never nest `{ }` inside a brace comment — the inner `}` ends the comment and the rest becomes code ("Error in declaration block"); (2) Altium keeps the .PrjScr/.pas OPEN in its Script editor after RunScript and re-runs the STALE in-editor copy after you overwrite the file — the bridge now renders `AdomAgent_<digest>.pas/.PrjScr` per source change; (3) a failed RunScript target opens the "Select Item To Run" picker (`TForm_RunScriptParameters`) and disables the main window — detect modals by "main window disabled + another enabled top-level window of the same PID", not by class.+- Foreground: the -R bootstrap activates Altium; the bridge captures the user's foreground before and hands it back immediately and during the wait (John: "stop foregrounding on me"). Restart the agent as rarely as possible — every restart is one activation.++## 6e. Phase 4 COMPLETE (2026-08-29) — library authoring++- `altium_create_footprint {name, libraryPath, pads:"des,x,y,w,h,rect|round,hole;…"}` — opens or CREATES the .PcbLib (`CreateNewDocumentFromDocumentKind('PCBLIB')` + `Doc.DoSafeChangeFileNameAndSave(path,'PCBLIB')`), `PCBServer.CreatePCBLibComp`, pads via `PCBObjectFactory(ePadObject…)` + `Comp.AddPCBObject`, `PLib.RegisterComponent`, `CurrentComponent`, `Board.ViewManager_FullUpdate`. 3.1 s, returns a frame.+- `altium_create_symbol {name, libraryPath, designator, bodyWidthMil, bodyHeightMil, pins:"des,name,left|right|top|bottom,offset;…"}` — `SchObjectFactory(eSchComponent)`, `eRectangle` body, `ePin`s with `Orientation`/`PinLength`, `SLib.AddSchComponent` + `RobotManager.SendMessage(... SCHM_PrimitiveRegistration ...)`. 1.1 s.+- `altium_save` — `WorkspaceManager:SaveObject ObjectKind=FocusedDocument`. Both libraries verified on disk.+- Release kit in the repo: `SKILL.md` (user skill), `README.md`, `package.json` (39 discovery triggers), `install.sh`/`uninstall.sh`, `CHANGELOG.md`, `scripts/release.py` (runtime zip 52 KB + manifest). NOT yet published — the wiki already has Barrett's `barrett-land/adom-desktop-altium-bridge` using name `altium`/prefix `altium_`; resolve rename-vs-supersede first.++## 6f. Barrett merge + taskbar (2026-08-29/30)++Ported from `barrett-land/adom-desktop-altium-bridge` (v0.0.55) into the resident agent — all measured working: `altium_set_params` (parameters incl. `LCSC Part #`, comment, footprint link via `ISch_Implementation.AddDataFileLink`), `altium_add_body` (extruded contour or `ModelFactory_FromFilename` STEP), `altium_install_library` (`IntegratedLibraryManager.InstallLibrary`), `altium_compile_intlib` / `altium_extract_intlib` (`CreateIntegratedLibrary` / `ExtractSources`). His **AdomBridge .NET extension** (prebuilt DLL + .ins/.rcs + Install-AdomBridge.ps1 + REMOVE cmd) is vendored under `src/extension/` and exposed as `altium_install_extension` (Altium CLOSED), `altium_extension_ping`, `altium_export_step` (headless STEP with `Holes=All|Copper|Models`), `altium_import_kicad` (no-wizard import). Dispatch is `X2.EXE -RAdomBridge:<Process>(Req=<space-free request file>)` — a server process, not a script, so it coexists with the resident agent. Facts from his page worth keeping: `-R` splits on whitespace (parameters go in a request file), OutJobs only generate when they are project members, STEP via OutJob no-ops headlessly, `Client.ShowDocumentDontFocus` exists (the agent's `open` now prefers it).++Taskbar: `handlers/taskbar.py` — overlay badge via ab `desktop_taskbar {overlay:{icon}}` on the first driving verb (cleared on idle), measured-estimate progress bar for verbs ≥3 s, no AUMID stamping. `altium_state.drivingBadge` reports `{active, lastError}`.++## 6g. Phase 5 RESULTS + the extension verdict (2026-08-30)++- **AdomBridge .NET extension (Barrett's) does NOT load into the running AD 26.9.1 instance.** Registered via his Install-AdomBridge.ps1 (registry node added cleanly, 20 items), Altium restarted → a `Could not start Vault Explorer` warning at startup; `X2 -RAdomBridge:Ping` helpers waited ~65–90 s on the *startup mutex* and then hosted the server in themselves (their own DXP_Startup.log says "Starting server AdomBridge"), the main instance never did; `RunProcess('AdomBridge:Ping')` from the resident agent returned in 16 ms and wrote nothing (unknown process → silently ignored). Unregistered again (`altium_install_extension {remove:true}`). Verbs kept, fail with `extension_timeout` + this note. Next step if wanted: rebuild against AD 26's in-box .NET assemblies (Barrett's `adom-altium-extension` project) and verify with a clean-profile Altium.+- **Altium 365 API on the Develop trial:** Admin shows Settings / Part Providers / Explorer / Usage and Billing / Extensions — **no Developer page**, every guessed token URL 404s. `altium_a365_*` verbs are wired for a token from a Pro/Enterprise workspace.+- Account/licence: `altium_signin_status`, `altium_license {action}` (image-space clicks on the WebView2 page with measured coords: refresh (1288,178), use_license (1252,388), start_trial (788,607) on a 1400×840 capture — verify against the returned shot), `altium_connect_workspace` (opens the `Not Connected` picker, `TWorkspacesForm`, custom list without UIA children → returns a shot to click).+- Saving example projects on close: Altium asks "Confirm Save Locally for (N) Modified Documents" (WPF grid: Save All / Save None / OK / Cancel, all UIA-invokable) — `altium_close` reports it; answer Save None + OK for scratch work.++## 6h. First end-to-end demo — Ray's CH340G (2026-08-30) + two bugs found++Imported `ray/ch340g` (WCH USB-serial, SOP-16) from the wiki into a NATIVE Altium library entirely through the bridge, then walked the surfaces. WORKED, all of that chip: `altium_create_footprint` (16 SOIC pads parsed from the page's pads.json, mm→mil), `altium_add_body` (the page's real 217 KB KiCad STEP), `altium_create_symbol` (16 named pins GND/TXD/RXD/UD+/VCC/…), `altium_set_params` (MPN/mfr/pkg + footprint link), `altium_show_symbol` / `_footprint` / `_library`, `altium_place_on_pcb` (new `place_on_pcb` verb: `IPCB_LibComponent.Replicate` → `Board.AddPCBObject` → the footprint lands on a real PcbDoc, 16 pads), `altium_show_2d_board`.++TWO REAL BUGS surfaced — fix before publishing:+1. **`place_on_sch` loses the symbol graphics.** `ISch_Component.Replicate` onto a fresh sheet placed the component (U1 designator + CH340G comment appear) but NOT its rectangle/pins — only the two text strings render. Setting `CurrentPartID:=1`/`DisplayMode:=0` on the copy did not fix it. The primitives inside the component group aren't being carried/registered onto the target sheet. Likely need to iterate the lib component's children and `AddSchObject` each onto the instance, or place via `IntegratedLibraryManager` rather than `Replicate`.+2. **STEP 3D body renders OFF-ORIGIN.** `add_body` with `ModelFactory_FromFilename` attaches (returns ok), but in both the library 3D view and on the board the body sits at the model's native origin (a small sliver at the board corner), not over the pads — so the 3D board shows bare gold pads. Reposition the body to the footprint centroid after `SetState_FromModel` (set `Body.ModelPosition`/move), or bake the offset.++Also: **`PCB:SetCamera(To=RotateFit_*)` is a no-op via RunProcess on AD 26.9.1** even with the window raised — the 3D view stays dead top-down. A real isometric needs either a focused numpad-key rotation (SendInput) or a different camera API; do not rely on SetCamera. Board substrate also renders white (no green plane) in the library/loose-PcbDoc 3D — a board shape + layer stack is needed for a proper 3D board.++Net for a demo today: symbol, footprint, library manager, schematic-with-part (text only), 2D board with the real footprint — all solid; 3D-with-body and schematic-graphics need the two fixes above.++## 6i. Demo fixes (2026-08-30) — schematic FIXED, one important bridge bug FIXED, 3D body still open++**FIXED — `place_on_sch` now renders the full symbol.** `ISch_Component.Replicate` does NOT re-parent a library part's pins/rectangle onto a normal sheet (only the designator/comment text placed). The working path: build a fresh `SchServer.SchObjectFactory(eSchComponent)`, iterate the lib component's children (`ePin/eRectangle/eLine/…`), `child.Replicate` each, set `OwnerPartId:=1`/`OwnerPartDisplayMode:=0`, `Inst.AddSchObject(child)`, then `RegisterSchObjectInContainer` + SendMessage. Proven on Ray's CH340G — 17 primitives (rect + 16 pins) render with all pin names on `CH340G_Final.SchDoc`. (Setting `OwnerPartDisplayMode` alone on the ORIGINAL symbol did NOT fix Replicate — the copy-primitives approach is required.)++**FIXED — `agent_start` silently ran STALE code.** The DelphiScript loop lives inside Altium; `bridge_kill` restarts the ab PROCESS but not the loop, and `start()` early-returned `alreadyRunning` when it saw the old loop's heartbeat — so redeployed agent code never loaded (every "fix" this session needed a manual `agent_stop` first). Fix: the agent stamps its generation (`AGENT_GEN` = template digest) into `hb.txt`; `start()` compares the resident `gen` to the current template digest and auto-stops+restarts a stale loop. ⚠ TWO traps hit while adding it: (1) `_agent_base()` must hash the RAW template, NOT `rendered_agent_source()` — the latter recursed and crashed the whole bridge; (2) editing `agent.py`/`server.py` needs a `bridge_kill` to reload the PROCESS, separate from restarting the in-Altium loop.++**STILL OPEN — 3D component body does not render over the pads in the PCB Library 3D view.** `add_body` attaches (STEP via `ModelFactory_FromFilename`, or an extruded contour) and returns ok, but neither shows as a solid over the pads in the footprint 3D editor — both appear as a small sliver in a corner. A post-add `MoveByXY(BoundingRectangle…)` centering attempt was WRONG (moved it further off) and has been reverted. Likely causes to chase next: STEP unit/scale on import; the PCB Library editor's "Display 3D Bodies" option; and bodies render reliably only on a real board (place the footprint on a PcbDoc WITH a layer stack + board shape, then view 3D) — the loose-PcbDoc/library 3D view is the weak path. Also `PCB:SetCamera(To=RotateFit_*)` is a no-op via RunProcess, so the view stays top-down regardless — a real isometric needs SendInput numpad keys to the focused 3D window. The Altium main window also self-MINIMIZED during the 3D switches; `desktop_set_window_state {state:restore}` fixes the sliver captures.++## 6j. 3D BODY FIXED end-to-end (2026-08-30) — five root causes, all measured++The chip now renders in 3D on a real board (black body + 16 gold pads on the green PCB, `CH340G_Board14.PcbDoc`). It took FIVE stacked fixes, each verified by data (`altium_pcb_report` — a new diagnostic verb that dumps per-component pads/bodies with layer + bbox in mils, plus a board object census by ObjectId):++1. **A primitive with no Layer is silently dropped.** `add_body` never set `Body.Layer`; pads always set theirs. Fix: `Body.Layer := eMechanical1` (+ enable `LayerStack.LayerObject[eMechanical1].MechanicalLayerEnabled` and `LayerIsDisplayed`) — and `Body.BodyOpacity3D := 1.0`.+2. **The stored lib component is NOT the editing surface.** `Fp.AddPCBObject(Body)` counted 1 body immediately but the body was GONE after save+reopen — the editor canvas copy overwrites the stored component. Fix: `PLib.CurrentComponent := Fp` FIRST, then add to the CANVAS (`PLib.Board.AddPCBObject(Body)`); the editor persists the canvas into the component on save. (`bodiesNow=1` AND survives reopen: `libBodies=1`.)+3. **Group `Replicate` of an `IPCB_LibComponent` DISSOLVES on a board** — census showed 16 free pads, no `eComponentObject`, no body. Fix (same cure as the schematic): fresh `PCBObjectFactory(eComponentObject)`, copy each child with primitive-level `Replicate` + `Inst.AddPCBObject`, and — critical — **fill the group BEFORE `Board.AddPCBObject(Inst)`**: children added to an already-registered component exist in data but never draw, whatever address they're robot-registered to.+4. **The lib stores pads around the canvas origin (~50000,50000 mil) but an authored body contour around 0,0** — one group move can't land both (body hit the target, pads landed 50000 mil away). Fix: normalize per cluster — compute the copied PAD bbox center and `MoveByXY` pads (and non-text children) by `target−center`; move each BODY to the target by its own bbox center.+5. **Assigning `Inst.x/y` AFTER moving children re-translates the whole group** (everything landed at 8000,6000). Anchor `Inst.x/y := target` BEFORE the normalization. Also hide `NameOn/CommentOn` (the factory "Designator1" text is huge and hijacks Zoom All; hidden texts still bloat the bbox — frame with `Board.GraphicalView_ZoomOnRect(...)` via eval instead of trusting Zoom All).++More traps measured on the way: `GetCurrentPCBBoard` answers with the LIBRARY's internal board when a PcbLib is focused (a board-reuse path placed into the lib canvas and renamed the lib doc — always create the PcbDoc and only then read GetCurrentPCBBoard); `GetCurrentPCBLibrary` answers even when a board is focused (pcb_report needs `want:"board"`); a nil `BoardOutline.BoundingRectangle`, `SetFileName` on an unassigned doc, and `GroupIterator_Create` on IPCB_Board all HALT the engine as "Undeclared identifier"; `IPCB_Component.Name.Text :=` halts too (use `NameOn`/`MoveToXY` on `Inst.Name`). GroupIterators need `AddFilter_LayerSet(AllLayers)` or mech-layer children are invisible to them. `WriteAtomic` stage breadcrumbs into the workspace (`agent/stage.txt`) pinpoint hangs.++Still open (minor): the PCB **Library** editor 3D view does not render bodies (the board 3D does — use `show_3d_chip` for selection, the placed-board flow for true 3D); `PCB:SetCamera` remains a no-op so views are framed with `GraphicalView_ZoomOnRect`; the schematic `place_on_sch` fix (§6i) still stands. Cleanup owed: the fix session littered `CH340G_v2..v8` libs and `CH340G_Board..Board15` docs in Downloads and many open tabs.++## 7. Sources++Altium docs: scripting overview/setup, running scripts, DelphiScript, Altium SDK overview, Altium+365 API quick start + API page, embeddable viewer, licensing, sign-in. Vendor CI article (X2 `-R`+recipe). Community: `salitronic/eda-agent`, `coffeenmusic/altium-mcp`, `ee-in-a-box/altium-copilot`,+`embedded-society/altium-designer-mcp`, `10xgenomicsEng/altium-mcp-unified`,+`oharown/mcp-codex-altium-designer`, `Altium-Designer-addons/scripts-libraries`, KiCad Altium+importer docs, `vadmium/python-altium`, `thesourcerer8/altium2kicad`.
uninstall.shadded+6
@@ -0,0 +1,6 @@+#!/usr/bin/env bash+set -euo pipefail+for HOME_DIR in "$HOME/.claude/skills" "$HOME/.codex/skills"; do+  rm -rf "$HOME_DIR/altium-bridge" "$HOME_DIR/altium-bridge-plan"+done+echo "OK: altium-bridge skills removed"