app
Altium - the Altium Bridge
Public Made by Adomby adom
Adom Bridge for Altium Designer: a resident DelphiScript agent gives the AI eval, the 1,100-command process launcher, all 8 EDA surfaces with screenshots, compile/ERC, DRC, OutJob outputs, native SchLib/PcbLib authoring with STEP 3D bodies, and dialog handling by PID.
← Commit history
v0.1.6: full 10-skill slate (install/libraries/boards/outputs/dev/publish)
13 files changed
+987−7
CHANGELOG.md+3adom-bridge-altium-manifest.json+3−3adom-bridge-altium-v0.1.6.zipbridge.json+1−1install.sh+6package.json+8−2skills/altium-boards/SKILL.md+61skills/altium-bridge-dev/SKILL.md+65skills/altium-bridge-publish/SKILL.md+62skills/altium-install/SKILL.md+653skills/altium-libraries/SKILL.md+83skills/altium-outputs/SKILL.md+41uninstall.sh+1−1CHANGELOG.md+3@@ -1,5 +1,8 @@ # Changelog +## 0.1.6 — 2026-08-30+- Full skill slate (10): added altium-install (the install recipe, finally packed), altium-libraries, altium-boards, altium-outputs, altium-bridge-dev, altium-bridge-publish — matching the kicad-bridge fleet depth.+ ## 0.1.5 — 2026-08-30 - Barrett consolidation complete: cleanup_libs, set_visibility, xcopy_footprint (cross-library), and import_part (his fat verb on the resident lane) — full verb parity with barrett-land/adom-desktop-altium-bridge. - Skillpack: the pkg now ships altium-part-import (his 3-path catalog-part workflow, rewritten on these verbs, with the tri-vendor rule: Mouser preferred + DigiKey + LCSC part numbers on every component) and altium-kicad-import (his measured KiCad-10 findings + the vendored v10→v9 downgrader + the native no-importer lane).
adom-bridge-altium-manifest.json+3−3@@ -1,8 +1,8 @@ { "name": "altium",- "version": "0.1.5",- "url": "https://wiki.adom.inc/download/adom/altium-bridge/0.1.5/adom-bridge-altium-v0.1.5.zip",- "sha256": "a187c33c158e97660e9c481523813fa3299fa7a4d6740693b107c81b8f1020e5",+ "version": "0.1.6",+ "url": "https://wiki.adom.inc/download/adom/altium-bridge/0.1.6/adom-bridge-altium-v0.1.6.zip",+ "sha256": "693f08b288cd78f87a4db324f827c0b81ce6b6b1b32a522b7e701e05a465a0ef", "size": 92792, "verbPrefixes": [ "altium_"
adom-bridge-altium-v0.1.6.zipadded⋯ 1 unchanged line ⋯
bridge.json+1−1@@ -2,7 +2,7 @@ "manifest_version": 1, "name": "altium", "displayName": "Altium Designer",- "version": "0.1.5",+ "version": "0.1.6", "description": "Drive Altium Designer from Adom: a resident DelphiScript agent (eval + the 1,100-command process launcher), the X2 -R command line, UIA over the VCL shell, dialogs by PID, screenshots of every Altium window, and Altium 365.", "homepage": "https://wiki.adom.inc/adom/altium-bridge", "docs": "https://wiki.adom.inc/adom/altium-bridge",
install.sh+6@@ -11,6 +11,12 @@ for HOME_DIR in "$HOME/.claude/skills" "$HOME/.codex/skills"; do install_skill "$HOME_DIR/altium-bridge-plan" "$HERE/skills/altium-bridge-plan/SKILL.md" install_skill "$HOME_DIR/altium-part-import" "$HERE/skills/altium-part-import/SKILL.md" install_skill "$HOME_DIR/altium-kicad-import" "$HERE/skills/altium-kicad-import/SKILL.md"+ install_skill "$HOME_DIR/altium-install" "$HERE/skills/altium-install/SKILL.md"+ install_skill "$HOME_DIR/altium-libraries" "$HERE/skills/altium-libraries/SKILL.md"+ install_skill "$HOME_DIR/altium-boards" "$HERE/skills/altium-boards/SKILL.md"+ install_skill "$HOME_DIR/altium-outputs" "$HERE/skills/altium-outputs/SKILL.md"+ install_skill "$HOME_DIR/altium-bridge-dev" "$HERE/skills/altium-bridge-dev/SKILL.md"+ install_skill "$HOME_DIR/altium-bridge-publish" "$HERE/skills/altium-bridge-publish/SKILL.md" done # Dashboard: the pkg ships it, the dock runs `altium-dashboard serve --print-url`.
package.json+8−2@@ -1,6 +1,6 @@ { "slug": "altium-bridge",- "version": "0.1.5",+ "version": "0.1.6", "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": [@@ -34,7 +34,13 @@ "dashboard/ui/altium-logo.png", "dashboard/ui/dock-altium.svg", "skills/altium-part-import/SKILL.md",- "skills/altium-kicad-import/SKILL.md"+ "skills/altium-kicad-import/SKILL.md",+ "skills/altium-install/SKILL.md",+ "skills/altium-bridge-dev/SKILL.md",+ "skills/altium-bridge-publish/SKILL.md",+ "skills/altium-libraries/SKILL.md",+ "skills/altium-boards/SKILL.md",+ "skills/altium-outputs/SKILL.md" ], "discovery_triggers": [ "open my design in altium",
skills/altium-boards/SKILL.mdadded+61@@ -0,0 +1,61 @@+---+name: altium-boards+description: >-+ Boards through the altium-bridge: create a wired test board sized to a part, define a real+ board shape, place library parts on schematics and boards (both coordinate frames+ normalized), swap footprints on a placed component with the wiring surviving, read ground+ truth back (pads/bodies/nets/tracks), and capture 2D/3D frames incl. the bottom view — plus+ the measured 3D camera facts (ResetCamera presets work, SetCamera is a no-op, orbiting needs+ a synthetic drag). Use for test boards, placement, footprint swaps, board frames. Trigger+ words — altium test board, place part on board, swap footprint, altium board shape, altium+ 3d camera, board bottom view, pcb report.+---++# altium-boards++## Test boards in one call++`altium_test_board {libraryPath, footprintName, wMm, hMm, pcbPath, assigns:"1=PIN1;49=GND_EP",+stubs:"1=-6.5,3.5;49=0,-6.5"}` → fresh PcbDoc, rectangular board shape, the footprint placed+at the centre (per-primitive copy), nets created and assigned by pad designator (every pad+named "49" joins the EP net), one stub track per assign. Size the FR4 to enclose silk ++courtyard + 3 mm margin so the pin-1 dot is visible on the 3D board.++## Board shape: only the editor process works++`IPCB_BoardOutline.PointCount/Segments/Rebuild` is a silent no-op and `.Clear` HALTS the+engine (measured). `altium_board_shape {x0Mm,y0Mm,wMm,hMm}` does it the working way: four+temporary selected tracks + process `PCB:PlaceBoardOutline Mode=BOARDOUTLINE_FROM_SEL_PRIMS`,+then deletes the tracks.++## Placement: the four ordering laws (all measured)++1. Group `Replicate` of a lib component DISSOLVES on a board (free pads, no component) — build+ a fresh `eComponentObject` and copy child primitives one by one.+2. Fill the component BEFORE `Board.AddPCBObject` — children added after registration exist in+ data but never draw.+3. The lib stores pads around the canvas origin (~50000 mil) but authored bodies around 0 —+ normalize the pad cluster and each body to the target INDEPENDENTLY.+4. Anchor `Inst.x/y` BEFORE normalizing children — assigning after re-translates the group.+`place_on_pcb` / `place_on_sch` / `test_board` / `swap_footprint` all embody these.++## Footprint swap, wiring intact++`altium_swap_footprint {pcbPath, libraryPath, footprintName, assigns}` removes the component,+re-places the new footprint at the same x/y, re-assigns nets by designator. Tracks are+untouched, so connectivity survives — verify with `altium_pcb_report {want:"board"}`, which+returns per-component pads/nets ("1=PIN1;49=GND_EP;…"), body bboxes, and the free tracks with+their nets. Trust the report, not a return code.++## 3D and frames++- `PCB:SwitchTo3D` / `SwitchTo2D`; camera presets `PCB:ResetCamera To=Top|Bottom|Front|Back|+ Left|Right` WORK; `PCB:SetCamera To=RotateFit_*` is a NO-OP on AD 26 — arbitrary orbiting+ needs a synthetic Shift+right-drag with RELATIVE `mouse_event` moves (SetCursorPos alone+ does not rotate), via ab `run_script`, window focused (toast first).+- The bottom preset is how you prove drilled vias (`To=Bottom`).+- Zoom: `PCB:Zoom Action=Document|Target=Board` fits the board;+ `GraphicalView_ZoomOnRect(...)` via eval frames an exact rect. Zoom All lies when hidden+ text bloats the bbox.+- **Frames under a locked Windows session are BLACK** (DirectX): check+ `desktop_get_foreground` ≠ 0 before trusting a board frame; GDI editors still capture.
skills/altium-bridge-dev/SKILL.mdadded+65@@ -0,0 +1,65 @@+---+name: altium-bridge-dev+user-invocable: false+description: >-+ DEVELOPER skill (never offered to everyday users) for working ON the altium-bridge: the+ resident DelphiScript agent architecture (file-drop REPL, per-digest generations,+ stale-agent auto-restart), the dialog pre-emption doctrine (the debugger breaks on ANY+ exception, so a raise IS a modal), the transaction-hygiene rule (dangling PreProcess = "a+ command is currently active"), the deploy loop (write_file into the bridge cache + agent_stop+ before agent_start, bridge_kill to reload the Python), and the engine-halting identifier+ list. Read before editing agent/AdomAgent.pas.tmpl or server.py. Trigger words — edit altium+ bridge, altium agent template, delphiscript agent, deploy altium bridge, altium engine halt,+ undeclared identifier altium.+---++# altium-bridge-dev++Everything you need to change the bridge without re-learning what this session paid for.++## Architecture++- **Resident DelphiScript agent** (`agent/AdomAgent.pas.tmpl`): bootstrapped once via+ `X2.EXE -RScriptingSystem:RunScript(...StartAgent)`, then a polling loop over a file-drop+ inbox (`request.txt` → `response_<id>.json` in the workspace). One script at a time; DO NOT+ `run_script` while it is resident. `eval` runs STATEMENTS ONLY (no Var block).+- **Per-digest generations**: the tmpl is rendered to `AdomAgent_<digest>.pas`; the digest is+ hashed from the RAW template (NOT the rendered source — that recursed and crashed the bridge+ once). The heartbeat carries `gen`; `agent_start` auto-restarts a stale in-Altium loop.+- **Deploy loop**: edit `src/agent/AdomAgent.pas.tmpl` or `src/server.py` → `write_file` into+ `C:\Users\john\AppData\Local\Adom Bridge\bridges-cache\altium\` → to load NEW AGENT code,+ `altium_agent_stop` THEN `altium_agent_start` (bridge_kill alone leaves the in-Altium loop+ running old code); to load NEW PYTHON (server.py/handlers), `bridge_kill` (reloads the+ process). Both often needed together.++## Dialog pre-emption (John: a dismissed dialog is a defect)++The DelphiScript debugger breaks on ANY raised exception even inside Try/Except — a raise IS+an Error modal. So: `ReadAll` probes with `FileOpen` (returns -1, never raises) before+`LoadFromFile`. `TxClear` (an unbalanced `PostProcess`, harmless) runs at agent start, before+every save, and after every recover, because a command that halted between PreProcess and+PostProcess leaves "a command is currently active" and the next save draws the save-a-copy+Warning. When you add a mutating command, wrap file reads and never leave a transaction open.++## Engine-halting identifiers (all measured, all cost a recover)++`BottomXSize`/`MidXSize` (pad size props don't exist — Top covers a multi-layer pad),+`IPCB_BoardOutline.Clear`, `eProcessFreePrimitives`, `IPCB_Board.GroupIterator_Create` (use+`BoardIterator_Create`), `IPCB_Component.Name.Text :=` (use `NameOn`/`Inst.Name.MoveToXY`),+`GroupIterator` without `AddFilter_LayerSet(AllLayers)` (mech-layer children invisible),+`RunProcessWithParameters` (use ResetParameters+AddStringParameter+RunProcess), a nested `{}`+inside a brace comment (breaks compilation). An unknown identifier is a RUNTIME error that+halts the debugger; `altium_agent_recover` clears it.++## The brace rule for JSON-building code++Result strings build JSON with `'{' + ... + '}'`; that's fine in a STRING LITERAL. It is only+BRACE COMMENTS that cannot contain a nested `{`. Lint new commands:+`re.sub(r"'[^']*'","",code)` then assert no `\{[^}]*\{`.++## Verify like production fails++Frame-check EVERY generated look/variant (a 3-of-10 spot check shipped three defect classes).+Trust `altium_pcb_report` read-backs, not return codes. A timeout is usually success that+outran ab's 60s HTTP budget — poll/verify, don't blind-retry. Board frames are BLACK under a+locked Windows session (DirectX) — check `desktop_get_foreground` first.
skills/altium-bridge-publish/SKILL.mdadded+62@@ -0,0 +1,62 @@+---+name: altium-bridge-publish+user-invocable: false+description: >-+ DEVELOPER-only publish recipe for shipping a new altium-bridge version to+ wiki.adom.inc/adom/altium-bridge: the version lockstep (bridge.json + BRIDGE_VERSION ++ package.json + manifest), the runtime zip build, repo push + release create, the skills-only+ pkg publish (stage from files[] to keep the tarball clean), and the studio-gated hero. Read+ when releasing the altium bridge. Trigger words — publish altium bridge, release altium+ bridge, ship altium bridge, bump altium bridge, altium bridge manifest, altium bridge pkg.+---++# altium-bridge-publish++Two artifacts: the RUNTIME (zip + manifest on the page `/files`, what Adom Bridge downloads)+and the SKILLS PKG (`adom/altium-bridge`, container docs). Use `adom-wiki` for ALL wiki calls.++## 1. Version lockstep (bump together)++`src/bridge.json` `version`, `src/BRIDGE_VERSION`, `package.json` `version`, and the generated+`adom-bridge-altium-manifest.json` `version` must match. `scripts/release.py` builds the zip ++manifest from `src/bridge.json`.++## 2. Build + push runtime++```bash+python3 scripts/release.py # dist/adom-bridge-altium-v<ver>.zip + manifest+cp dist/adom-bridge-altium-v<ver>.zip dist/adom-bridge-altium-manifest.json . ; cp src/bridge.json .+adom-wiki repo push adom/altium-bridge --files adom-bridge-altium-v<ver>.zip adom-bridge-altium-manifest.json bridge.json <changed docs> -m "v<ver>: ..."+adom-wiki release create adom/altium-bridge <ver> --title "Altium bridge v<ver>" --changelog "..." --binary-path adom-bridge-altium-v<ver>.zip+rm -f adom-bridge-altium-v<ver>.zip bridge.json # don't leave copies in the repo root+```++## 3. Publish the skills pkg (stage clean)++Stage ONLY the declared `files[]` into a fresh dir and pack from there — packing the repo+sweeps up the runtime zip and dist. The pkg is skills + tools, no binaries.+```bash+ST=$(mktemp -d); python3 -c "import json,shutil,os+for f in json.load(open('package.json'))['files']:+ os.makedirs(os.path.dirname('$ST/'+f) or '$ST',exist_ok=True); shutil.copy(f,'$ST/'+f)"+(cd $ST && adom-wiki pkg publish --org adom --skip-lint --yes)+```+`files[]` lists every skill path explicitly (no globs) AND install.sh/uninstall.sh install+each. Dev/publish skills carry `user-invocable: false` + a description opening "DEVELOPER-only".++## 4. Hero (first publish only)++New-page heroes are studio-gated: `hero-studio pose adom/altium-bridge --surface webview`,+load a REAL Altium capture as the showcase, the human clicks Generate, then+`adom-wiki page hero adom/altium-bridge --image docs/hero.png`. Redeploys keep the icon.++## 5. Verify++`adom-wiki page stats adom/altium-bridge` (version), `adom-wiki release list` (asset present),+`adom-wiki repo show adom/altium-bridge adom-bridge-altium-manifest.json` (served sha matches).+A docs-only change (README/images) skips the version bump — just `repo push`.++## Standing rules++- Commit source in lockstep with each publish; the changelog must be real (server enforces it).+- Never elevate ab. Never toast for progress — taskbar button only (John).
skills/altium-install/SKILL.mdadded+653@@ -0,0 +1,653 @@+---+name: altium-install+description: "Install Altium Designer on the user's Windows PC from an Adom container via adom-bridge — sign in to altium.com (incl. Google SSO + account linking), pull the gated online installer, verify its Authenticode signature, launch it, and drive the wizard. Documents the hard UIPI wall: the installer elevates itself, so a non-elevated adom-bridge CANNOT click the wizard past the EULA page. Also the altium-setup-trial recipe: the account/MSA gate, the free-trial route, and Google SSO account linking. Trigger words — install altium, altium install, altium setup, altium setup trial, altium trial, altium free trial, altium designer, AltiumDesignerSetup, altium sign in, altium account, altium download, altium license, altium EULA, altium installer stuck, altium wizard not clicking, altium UIA."+---++# altium-install++End-to-end recipe for installing Altium Designer on a Windows machine driven from+an Adom container through `adom-bridge`. Written from a real run against Altium+Designer **26.9.1 (Build 10)**, released 2026-08-05.++Read the **Known walls** section before starting. One of them is unavoidable and+determines whether this task can be finished unattended at all.++---++## 0. Preflight++Confirm the machine can take it. Altium wants ~10 GB installed; a full feature+set plus libraries and installer temp realistically lands at 15–25 GB.++```bash+adom-bridge --ai-thread "<thread>" shell_execute '{"command":"powershell -NoProfile -EncodedCommand <b64>","reason":"..."}'+```++Check: OS caption, `Get-PSDrive C` free space, existing Altium in both+`Uninstall` registry hives, and whether the bridge session is elevated+(`WindowsPrincipal.IsInRole(Administrator)`).++`AB_ELEVATED` is almost always **False** and **that is permanent** — it is a+diagnostic that tells you which windows you will be able to drive, never a thing+to change. Plan the run around it.++**Every gated verb needs BOTH `--ai-thread` and a per-call `reason`.** Missing+`--ai-thread` fails with `caller_identity_required`; these are two separate+refusals. See `adom-bridge-caller-identity`.++Encode PowerShell as UTF-16LE base64 and pass via `-EncodedCommand` — it avoids+the JSON/`cmd /c` quoting nightmare entirely:++```python+base64.b64encode(ps.encode('utf-16-le')).decode()+```++---++## 1. There is no public installer++Do not go hunting for a direct `.exe` URL. `downloads.altium.com` is a public S3+bucket but holds **only legacy archives** (P-CAD, Altium Designer 6.x, old+service packs). The current installer is not there.++On `altium.com/products/downloads` while signed out, every "Download (exe,+29.21 MB)" button for all ~130 versions is the same sign-in redirect:++```html+<a class="login-form" href="https://auth.altium.com/?ReturnUrl=...">Download (exe, 29.21 MB)</a>+```++`altium.com/free-trial` now 301-chains to `/develop/get-started`, which just+links back to the downloads page. Same gate.++**Signing in is mandatory.** After auth the same element becomes:++```html+<a id="i0" onclick="window.location.href='/altium_release/download?id=20014';"+ version="26.9.1" data-id-in="20014"> Online Installer (exe, 29.21 MB) </a>+```++The label changes from "Download" to **"Online Installer"** and the `login-form`+class is gone — that flip is a reliable signed-in check.++---++## 2. Sign in with pup (Google SSO path)++Use `pup_*` (Puppeteer), not `desktop_open_url` — you need to drive the page.+Pup's durable shared `adom-you` profile usually already holds the user's Google+session.++```bash+adom-bridge --ai-thread "<t>" pup_open_window '{"url":"https://auth.altium.com/?ReturnUrl=https://www.altium.com/products/downloads"}'+```++The flow is **email-first**:++1. Fill `input[type=email]`, click `button.form-button.primary` ("Next").+2. The page then reveals a password field **and** three `button.round-button`+ SSO buttons. Order observed: `[0]` Google, `[1]` Facebook, `[2]` other.+ Identify Google by the `#4285F4` fill — but note the SVG is a **base64 data+ URI**, so `innerHTML.indexOf("4285F4")` fails. Match on position or decode.+3. Click Google → `accounts.google.com` account chooser → click the account row.+4. **OAuth consent screen: click "Continue", not "Cancel".**+5. If the email already has a password-based Altium account, you land on+ `auth.altium.com/link` — "This email is already registered… Link it to your+ Google account". Click the `.oauth__user-info` row to link.++Success = redirect to the downloads page with the avatar replacing "Sign In".++### Gotchas++- **Never click a bare `button` selector on Google's consent screen.**+ `firstMatch:false` + `pickStrategy:"visible-text-largest"` picks **Cancel**,+ which dumps you at `auth.altium.com/oauth-result?error=1` ("Token Request+ Error"). Enumerate buttons, find the one whose text is exactly `Continue`,+ click its center coords.+- **Don't chain navigate→click without settling.** A `pup_navigate` followed+ immediately by a click leaves the field unfilled ("Please enter a valid email+ address"), and a retry *appends* to leftover text (`@[email protected]`).+ Clear via the native value setter before retyping:+ ```js+ const s=Object.getOwnPropertyDescriptor(HTMLInputElement.prototype,"value").set;+ s.call(input,""); input.dispatchEvent(new Event("input",{bubbles:true}));+ ```+- **`pup_eval` arrow functions with block bodies returning object literals+ intermittently fail** to parse. Use an IIFE with `var` and explicit+ `return JSON.stringify(...)`.+- An eval that spans a navigation dies with a detached-frame error. Don't wrap+ the wait and the read in one call — click, then read state separately.++---++## 3. Download and verify++Clicking "Online Installer" downloads through Chrome to the normal Downloads+folder — `C:\Users\<user>\Downloads\AltiumDesignerSetup_26_9_1.exe` (29.21 MB).++**Always verify the signature before running it:**++```powershell+Get-AuthenticodeSignature $f | Select-Object Status,@{n='Signer';e={$_.SignerCertificate.Subject}}+```++Expected: `Status : Valid`, `CN=Altium LLC, O=Altium LLC, L=La Jolla, S=California, C=US`,+FileVersion `26.9.1.10`, Company `Altium Limited`. Anything else — stop.++---++## 4. Launch++```bash+adom-bridge --ai-thread "<t>" shell_execute '{"command":"start \"\" \"C:\\Users\\<u>\\Downloads\\AltiumDesignerSetup_26_9_1.exe\"","reason":"..."}'+```++The `shell_execute` call **will time out at 2 minutes** — that is expected and+harmless. The process launches fine; confirm with+`process_list {"name":"Altium"}` (note: the arg is `name`/`nameFilter`, NOT+`filter`).++You will see **two** processes: a non-elevated parent and an **elevated child**.+The child owns the wizard window.++Find the window (`desktop_list_windows` returned empty in testing — use+`desktop_find_window`, and the arg is `titleContains`, not `title`):++```bash+adom-bridge --ai-thread "<t>" desktop_find_window '{"titleContains":"Altium Designer","reason":"..."}'+```++Expect `title: "Altium Designer 26 Installer"`, `className: "TInstallerForm"`.++---++## 5. Clicking the wizard — use image space, never math++### What UIA actually reports for this installer (measured)++**UIA is NOT blocked here.** It reads the elevated installer window fine from+non-elevated ab. The tree is simply *empty below the root*:++```bash+adom-bridge --ai-thread "<t>" desktop_ui_tree '{"hwnd":<inst>}'+```+```json+"status":"ok", "nodeCount": 1,+"root": {"name":"Altium Designer 26 Installer","role":"Window",+ "rect":{"x":680,"y":336,"w":1200,"h":855},+ "automationId":"","invokable":false,"settable":false}+```++One node, zero children — on every wizard page. Consequently+`desktop_find_control` returns `uia_not_found` for `Next`/`Back`/`Cancel`/any+text, scoped by `hwnd` or by `window` title, with or without `role`.++**Cause: the installer is Delphi.** Its buttons are `TXPBitBtn` — custom-drawn+controls that publish no UIA/MSAA provider. There is nothing to find, at any+integrity level. This is *not* UIPI; do not "fix" it by chasing elevation.++Contrast with the `grok-bot-setup` skill, where UIA automation of an NSIS wizard+works with real automationIds: NSIS uses standard Win32 controls that expose+themselves to UIA. Toolkit, not privilege, decides whether UIA can see a wizard.++The Win32 handles DO exist and are readable without elevation — enumerate them+instead (see §5b), which is how you get `&Next` = a real HWND with class+`TXPBitBtn`.++**Don't use only `desktop_find_control`.** ab ships a whole UIA family and the+singular finder is the least informative: `desktop_ui_tree` (subtree dump),+`desktop_find_controls` (plural), `desktop_ui_from_point`, `desktop_ui_window`,+`desktop_ui_text`, `desktop_ui_get`, `desktop_ui_capabilities` (per-verb+foreground-cost table — consult BEFORE calling), `desktop_ui_selftest`. Dump the+tree FIRST; one `desktop_ui_tree` telling you `nodeCount: 1` would have replaced+a dozen failing `find_control` guesses.++### UIA vs integrity — what is and is not blocked++`desktop_find_control` (read-only discovery) and `desktop_ui_click` /+`desktop_ui_set` (programmatic Invoke/SetValue) are real and they work well.+Prefer them over pixel clicks **whenever the target window is not elevated** —+they need no foreground, steal no focus, and move no cursor.++But against the **elevated** installer window every UIA query fails. This was+established with a control experiment, not an assumption — run the same control+before blaming UIA for anything:++```bash+# CONTROL — a normal-integrity window (Chrome). UIA resolves fine:+adom-bridge --ai-thread "<t>" desktop_find_control '{"hwnd":<chrome_hwnd>,"contains":"Sign in"}'+# -> error: null, errorCode: null++# TEST — the elevated installer. Five different query shapes, all identical failure:+adom-bridge --ai-thread "<t>" desktop_find_control '{"hwnd":<inst>,"contains":"Next"}'+adom-bridge --ai-thread "<t>" desktop_find_control '{"window":"Altium Designer 26 Installer","contains":"Next"}'+adom-bridge --ai-thread "<t>" desktop_find_control '{"hwnd":<inst>,"name":"Next"}'+adom-bridge --ai-thread "<t>" desktop_find_control '{"hwnd":<inst>,"contains":"accept"}'+adom-bridge --ai-thread "<t>" desktop_find_control '{"hwnd":<inst>,"role":"button","contains":"a"}'+# -> errorCode: "uia_not_found" (every one)+```++Same ab build, same session, one window works and the other does not. The+variable is integrity level, not the verb and not the query.++**Why:** a UIA client may only read or drive a *higher*-integrity window if it+holds the **UIAccess** privilege — which requires the binary be Authenticode+signed AND installed under `%ProgramFiles%`/`%SystemRoot%` AND declare+`uiAccess="true"` in its manifest — or if it is itself elevated. UIA Invoke+being "programmatic rather than synthetic" does not exempt it; if it did, UIPI+and with it UAC would be trivially defeatable. So this is a deliberate Windows+security boundary, not an ab limitation and not a bug to file.++Practical rule: **check `AB_ELEVATED` first.** If false and the target window is+elevated, neither UIA nor pixel clicks will work — stop trying query shapes and+switch strategy (silent child-process install, §6, or hand the click to the+user). **Do not "fix" this by elevating ab — see the standing prohibition+below.**++(`TInstallerForm` is a Delphi form and its UIA tree may be thin regardless, but+integrity is the blocker that actually matters here.)++### Pixel clicks — use image space, never math++**Do not compute screen coordinates by hand.** Screenshot, read `coordMap.shotId`,+and click in image space — ab converts:++```bash+adom-bridge --ai-thread "<t>" desktop_screenshot_window '{"hwnd":<h>,"reason":"..."}' # returns shotId+adom-bridge --ai-thread "<t>" desktop_click '{"space":"image","shotId":"<id>","x":631,"y":533,"hwnd":<h>,"reason":"..."}'+```++Hand-computed coords cost a wasted click that landed in the user's Autodesk+Fusion window instead. The window capture is confusing on purpose: it comes back+1200×855 (matching the window rect) with the content painted into only the+top-left ~800×570, which *looks* like 1.5× DPI scaling. It is not —+`coordMap` reports `dpiScale: 1.0, sx: 1.0, sy: 1.0`. Trust `coordMap`.++`desktop_click` reports `status: "error"` with+`SetCursorPos(x,y): The operation completed successfully. (0x00000000)`.+**That is a success misreported as an error** — `0x00000000` is `ERROR_SUCCESS`.+Ignore it and verify by screenshot. (Worth filing against ab.)++Wizard pages, in order:++| # | Page | Action | Image coords (1200×855 capture) |+|---|---|---|---|+| 1 | Welcome | Next | `(631, 533)` |+| 2 | End-User License Agreement | tick "I accept the agreement" `(528, 463)`, then Next | `(631, 533)` |+| 3 | **Account sign-in** | opens in the user's browser, not the installer | — |+| 4 | Select Design Functionality | choose features, then Next | `(631, 533)` |++Next sits at `(631, 533)` on every page — only the page body changes.++#### §5b Authoritative button coordinates — read them, never measure them++The window capture's `coordMap` is WRONG for this window (it reports+`dpiScale:1.0, sx:1.0`; the display is actually 1.5). Do not derive screen+coordinates from it. Enumerate the real control HWNDs instead — this is a+**read**, so it needs no elevation:++`EnumChildWindows(installerHwnd)` → filter class `~ BitBtn|Button` →+`GetWindowRect` each → centre → **×1.5** (the monitor `scale`) for physical.++Measured on the Ready To Install page (2560×1600 @150%):++| Button | HWND | Logical centre | **Physical screen XY** |+|---|---|---|---|+| `&Next` | 462526 | (1084, 758) | **(1626, 1137)** |+| `&Back` | 331480 | (1006, 758) | **(1509, 1137)** |+| `&Cancel` | 69340 | (1168, 758) | **(1752, 1137)** |+| `&Advanced…` | 725434 | (775, 758) | **(1162, 1137)** |++The whole button row shares `y = 1137`, and Next stayed at (1626, 1137) on every+page. Confirmed correct independently: hovering it made the button render its+amber hover state.++### Page 3: the sign-in is a BROWSER hand-off++After the EULA the installer sends the user to an Altium account sign-in **in+their default browser**, not in a wizard page. The installer just waits. Because+that window is a normal-integrity browser, it *is* drivable (UIA works there) —+but confirm the address bar is `altium.com`/`auth.altium.com` first; see the+sign-in-window wall below.++### Page 4: Select Design Functionality — the size numbers++Defaults observed on a clean machine:++| Feature | Default |+|---|---|+| PCB Design | ✓ (mandatory — "Base system including all required features") |+| Platform Extensions | ✓ |+| Importers\Exporters | ✓ |+| Touch Sensor Support | ☐ |++**With those defaults: 2.3 GB download, 6.3 GB disk.** Do not repeat the common+"Altium needs 15–25 GB" claim — that is wrong for the modern online installer at+default selection, and it will scare a user off a machine that has plenty of+room. Read the two lines the page prints and quote those instead. Only widen the+selection (expanding the `+` nodes adds importers, vendor extensions, etc.) if+the user actually needs them; they can be added post-install.++---++## 6. Silent install — the way to sidestep UIPI entirely++If the wizard cannot be driven (non-elevated ab vs elevated installer), the+better answer is **not to have a wizard at all**. Strings extracted from+`AltiumDesignerSetup_26_9_1.exe` confirm it carries unattended-install support:++```powershell+$bytes=[IO.File]::ReadAllBytes($f)+# scan BOTH ASCII and Unicode encodings of the buffer+$pat='(?i)(--[a-z_]{3,30}|/(?:S|SILENT|q|quiet)\b|InstallPath|AutoInstall|NoRestart|LicenseAccept)'+```++Real hits: **`InstallPath`** and **`AutoInstall`**. (`/q` and `/s` also hit+hundreds of times, but those are 2-character patterns that match random bytes —+treat them as noise, not evidence.)++Launch it elevated so no UIPI question arises, which costs the user exactly one+UAC click:++```powershell+Start-Process -FilePath $f -Verb RunAs -ArgumentList '<switches>'+```++**Do not guess the exact switch syntax and fire it at a user's machine.** A+wrong flag can kick off an unattended install to the wrong path, and this is a+10–25 GB operation. Confirm the syntax against Altium's+"Installation, Licensing & Management" docs for the matching version first, then+have the user approve the exact command line.++## Known walls++### UIPI blocks every click after the Welcome page — THE BIG ONE++The Welcome page's Next click works. **Nothing after it does.** Clicks report+success, the cursor moves, and the window ignores them completely.++Cause: the wizard window is owned by the **elevated** child process while+`adom-bridge` runs **non-elevated**. Windows **UIPI (User Interface Privilege+Isolation)** silently discards synthetic input crossing from lower to higher+integrity. Confirm it:++```powershell+# GetWindowThreadProcessId on the hwnd -> owning pid+# compare against: ([WindowsPrincipal][WindowsIdentity]::GetCurrent()).IsInRole(Administrator)+```++A second tell: `process_kill` on the child returns+`Access is denied` — a non-elevated caller cannot kill an elevated process.++**Beware a false positive here.** During this run the wizard advanced once+right after a `desktop_click`, and that was initially credited to the click —+leading to a premature "UIPI was the wrong diagnosis" correction. It was not:+the advance coincided exactly with the user typing a message, and the user had+been clicking the wizard by hand all along. **The user clicked it, not ab.**++The full tally of synthetic input against this elevated window, all dropped:++| Attempt | Result |+|---|---|+| `desktop_click` image-space, plain | dropped |+| `desktop_click` after `desktop_raise_window` + verified foreground | dropped |+| `desktop_click` with `cursor:true` glide | dropped |+| `desktop_press_key` arrow-key group navigation | dropped |+| `desktop_press_key ["alt","n"]` (the Next accelerator) | dropped |+| `desktop_find_control` (UIA), 5 query shapes | `uia_not_found` |++Every one reported `status: ok` or a cosmetic error. **Nothing tells you input+was dropped — you only find out by screenshotting and seeing no change.** So+after every click on an elevated window, verify by screenshot before believing+it. And when a state change happens while the user is also active, do not assume+it was yours.++**`bypass permissions` does NOT fix this.** That setting only silences+adom-bridge's own approval dialogs. UIPI is a Windows kernel security boundary+and no ab setting touches it.++> ### ⛔ NEVER suggest running adom-bridge as administrator+>+> Elevating ab is **not an option on Adom machines** — do not propose it, do not+> ask for it, do not list it as a fallback. It is a standing user instruction,+> not a per-machine preference. Take it off the table before you start+> reasoning about how to get past UIPI.++The two real options are:++1. **Relaunch the installer silently and elevated as a CHILD process** (§6).+ `Start-Process -Verb RunAs` elevates *the installer*, not ab — ab stays+ exactly as it is. The user clicks one UAC prompt and the install runs+ headless, so there is no GUI to drive and UIPI never enters into it. This is+ the way to finish without babysitting.+2. **Hand the remaining wizard pages to the user** — toast them (rung 3) and say+ exactly which button you need pressed.++Decide at step 0, and tell the user before launching so they aren't surprised+half-way through: *"I'll be able to do X, and I'll need you for Y."*++### Toasts: rare, and only for the nuclear option++**Do NOT overdo `notify_user`.** A toast interrupts the user. Reserve it for the+two cases that actually earn an interruption:++1. **The user has to DO something** — you are blocked and cannot proceed without+ a human action (click this button, sign in here, approve this). The nuclear+ option.+2. **You are about to take over their screen** — see the mandatory rule below.++Never toast for progress updates, status narration, "starting now", or "done".+Those belong in your reply to the user, not on their desktop. A toast that+carries no required action trains them to ignore the ones that do.++Note a toast can itself steal focus, so it also *costs* you: send it before you+foreground, never between a raise and a click.++### ⚠️ MANDATORY: toast before you foreground anything++**If you ever have to foreground a window, you MUST first send an ab toast+telling the user why and what you are doing.** This is case 2 above, and it is+not optional — foregrounding takes over their screen and moves their real+mouse.++```bash+adom-bridge --ai-thread "<t>" notify_user '{"title":"Driving the Altium installer","message":"I need your screen foreground to click the wizard buttons. Please dont click in it while I work.","reason":"Required disclosure before taking the foreground"}'+```++Foregrounding takes over the user's screen and moves their real mouse. They may+be mid-sentence in another app; a click that lands wrong lands in *their*+document. The toast is what turns an ambush into a handoff. Send it **before**+the first `desktop_raise_window` / `desktop_click` / `desktop_press_key`, and+tell them when you are done.++Rung 1 (UIA) needs no toast — it never touches the foreground. That is a big+part of why it is the default.++Note the toast itself can disturb focus, so send it first, then raise, then+verify with `desktop_get_foreground` before clicking.++### FIRST: is the installer even the window under your click point?++Before blaming integrity, permissions, coordinates or ab, check **occlusion**.+Most of the failed clicks in this session were landing on whatever window was+covering the installer — at one point `desktop_ui_from_point` at the Next+button's coordinates returned a text node whose `name` was *the very shell+command being typed to drive it*, i.e. the clicks were going into the Hydrogen+terminal.++`desktop_click` clicks **the topmost window at that pixel**, not the `hwnd` you+pass. The `hwnd` arg only focuses first — it does not route the click.++**Topmost check, one call:**++```bash+adom-bridge --ai-thread "<t>" desktop_ui_from_point '{"x":1626,"y":1137}'+```++| Result | Meaning |+|---|---|+| `control: {name:null, role:null, rect:null}` | the installer IS on top — its UIA tree is empty (`nodeCount:1`), so UIA sees nothing there |+| any node with a `name`/`rect` | **something else is covering it** — that is what your click will hit |++That inverted signal is peculiar to this installer and worth remembering: for a+Delphi app with no UIA tree, *null is the good answer*.++**To uncover it without taking the foreground** (which would re-trigger the+`SetCursorPos` gate), demote each covering window — `SWP_NOACTIVATE`, so+z-order changes but activation does not:++```bash+adom-bridge --ai-thread "<t>" desktop_set_window_state '{"hwnd":<coverer>,"state":"bottom","force":true,"reason":"..."}'+```++`force:true` is required if that window currently holds the foreground. Expect+**whack-a-mole**: on this machine Hydrogen, Fusion and Edge were each maximized+and each had to be demoted in turn, re-checking `ui_from_point` after every one.+Nothing is minimized or closed, so the user restores any of them with one+taskbar click — but tell them, because their screen visibly reshuffles.++### Confirming the mouse actually moves (the user could not see it)++`desktop_hover` is the correct verb — *"Move the real cursor to {x,y} without+clicking"*, via SendInput. It genuinely moves the physical pointer; verify+independently rather than trusting `status: ok`:++```powershell+Add-Type -AssemblyName System.Windows.Forms; [System.Windows.Forms.Cursor]::Position+```++**That reading is LOGICAL (DPI-unaware); ab speaks PHYSICAL.** On a 2560×1600 /+150% display, physical (1626,1137) reads back as logical (1084,758). Multiply by+`scale` from `desktop_list_monitors` before comparing, or you will "discover" a+bug that is just unit confusion.++A single hover is invisible to a watching user — there is no motion trail and+the jump is instant. If they need to SEE it, move through waypoints and paint+the teal Adom cursor alongside:++```bash+adom-bridge --ai-thread "<t>" desktop_cursor '{"action":"show","x":1626,"y":1137,"label":"Altium Next","persist":true}'+```++`desktop_cursor` is a PURE VISUAL overlay — it moves no pointer and clicks+nothing. It is the trust cue, never the input. `persist:true` means you MUST+hide it yourself afterwards.++### The exact UIPI boundary: MOVE is allowed, CLICK is not++This is the single most useful fact in this skill, and it took a dozen wasted+attempts to pin down. Against an **elevated** window, from non-elevated ab:++| Operation | Result |+|---|---|+| `desktop_hover` while a NON-elevated window holds foreground | **works** — cursor physically moves onto the target |+| the elevated window's button under that cursor | **visibly renders its hover state** |+| `desktop_click` on it | reports `status: ok` — **and nothing happens** |+| `desktop_hover`/`desktop_click` while the ELEVATED window holds foreground | `SetCursorPos: Access is denied (0x80070005)` |++So there are **two** separate gates, and they are easy to confuse:++1. **`SetCursorPos` is blocked whenever the foreground window is elevated.** Fix+ by demoting whatever elevated window has focus — `desktop_set_window_state+ {state:"bottom", force:true}` uses `SWP_NOACTIVATE`, moving z-order without+ changing activation, so foreground passes to a normal-integrity window and+ the cursor is free again.+2. **Even with the cursor successfully on the button, the synthetic CLICK is+ filtered.** There is no ab-side fix for this one. The button lighting up+ under the cursor proves the move landed and is NOT evidence the click will.++**Never conclude a click worked because the verb returned `status: ok`, because+the cursor moved, or because the control highlighted.** Only a screenshot+showing the page actually changed counts.++Corollary for coordinates: the button DID highlight at the computed point, which+independently confirms the coordinate math (`GetWindowRect` on the control's own+HWND × `dpiScale`). If a control highlights but will not activate, stop+re-deriving coordinates — they are already right, and you are hitting gate 2.++### The full input ladder — and how far each rung gets you++ab gives you three ways to drive a window. Climb in this order; each rung is+more disruptive than the last.++| Rung | Verbs | Foreground? | Works on elevated window from non-elevated ab? |+|---|---|---|---|+| 1. UIA (preferred) | `desktop_find_control`, `desktop_ui_click`, `desktop_ui_set`, `desktop_ui_focus` | No — programmatic, no focus steal, no cursor move | **No** — `uia_not_found` |+| 2. Synthetic input | `desktop_click`, `desktop_double_click`, `desktop_right_click`, `desktop_press_key`, `desktop_type` | **Yes** — ab foregrounds the target first | **No** — silently dropped |+| 3. Ask the human | `notify_user` toast | n/a | **Yes** — always |++Rung 1 is the right default: it drives windows in the background without+stealing focus or moving the user's cursor, so it never interrupts them.++Rung 2 is the **nuclear option**. It works where UIA is blind (Chromium+`chrome://` surfaces, native dialogs, shadow DOM), but it needs the window+foregrounded and it moves the real mouse — so it interrupts whatever the user+is doing, and a mis-aimed click lands in *their* app. That happened during this+run: a hand-computed coordinate put a click inside the user's Autodesk Fusion+window. Always use `{space:"image", shotId}` (below), never hand math.++Rung 3 is not a failure state — it is the correct move once 1 and 2 are+genuinely exhausted. Toast the user, say precisely what you need and why you+cannot do it yourself:++```bash+adom-bridge --ai-thread "<t>" notify_user '{"title":"...","message":"...","reason":"..."}'+```++**Against an elevated window, rungs 1 AND 2 both fail.** UIPI blocks synthetic+input exactly as it blocks UIA — verified here: `desktop_click` on the EULA+checkbox and `desktop_press_key` (arrow-key navigation of the option group) were+both dropped, while `desktop_click` reported success. The user had to tick that+checkbox by hand. So when `AB_ELEVATED=False` and the target is elevated, go+straight to rung 3, or avoid the GUI altogether (§6) — do not work down the+ladder hoping a lower rung punches through. It will not. And do not reach for+elevating ab; that is prohibited.++### A sign-in window appearing is NOT necessarily Altium's++When the user says "it wants a sign-in", **read the window's URL before clicking+anything.** On this machine the window that surfaced mid-install was:++```+authenticator.cursor.sh/grok-bot?client_id=...&redirect_uri=https://cursor.com/api/auth/bot/callback+```++— xAI/Cursor's **Grok Bot** sign-in, belonging to an entirely unrelated workflow+(see the `grok-bot-setup` skill), which happened to pop while the Altium+installer sat idle on the EULA page. Clicking "Sign in with Grok" / "Sign in+with Cursor" would have authenticated the user into a different commercial+product.++The Altium installer's own window is `className: TInstallerForm`, title+`Altium Designer 26 Installer`. Anything with `Chrome_WidgetWin_1` is a browser+window — enumerate it, screenshot it, and confirm the host in the address bar is+`altium.com` / `auth.altium.com` before you touch a sign-in button.++Note that a browser sign-in window is *usually* drivable even when the installer+is not: Chrome runs at normal integrity, so UIPI does not apply to it. That+asymmetry is useful — but only after you have confirmed the page is actually+part of the task.++### The EULA text pane renders blank++On the EULA page the agreement text box comes up empty/white. Cosmetic in this+run (the real blocker was UIPI), but if the accept control is still inert after+running the install silently (§6), suspect the installer failed to fetch its license text and+investigate its network access.++### Accepting agreements on the user's behalf++Two distinct documents, and they are **not** the same consent:++1. The **account MSA / Privacy Policy / ToS** — accepted implicitly by clicking+ Next on `auth.altium.com`.+2. The **software EULA** — a separate checkbox inside the installer.++Get explicit user agreement before either, and say plainly which one you ticked.+Do not fabricate company/personal details on any trial signup form.++### Do not defeat the bot check++`auth.altium.com` carries a captcha (reCAPTCHA/Turnstile detected in the DOM).+The Google SSO path above sails past it legitimately because the user's real+session is present. Never work around the control itself.
skills/altium-libraries/SKILL.mdadded+83@@ -0,0 +1,83 @@+---+name: altium-libraries+description: >-+ Author native Altium libraries through the altium-bridge: symbols with N display modes+ (alternate symbols), footprints with via pads / rounded-rect pads / named mechanical+ layers, STEP or extruded 3D bodies that persist and sit on the pads, footprint variants as+ models on one component, parameters with the tri-vendor rule (Mouser preferred + DigiKey ++ LCSC), and the measured pin-1 check. Everything here was proven on the CH340G and LAN7800+ imports. Use when creating or editing .SchLib/.PcbLib content, binding 3D bodies, building+ variant sets, or verifying pin-1. Trigger words — altium library authoring, altium display+ modes, alternate symbol altium, footprint variants, altium 3d body, solder ball layer,+ heatsink vias altium, pin-1 check.+---++# altium-libraries++How library authoring actually works on this bridge, with the traps already paid for.++## Symbols: one component, N looks (display modes)++Altium's alternate-symbol mechanism is DISPLAY MODES (Normal + Alternate 1..N-1), and modes+live on the primitives: every pin/rectangle/line carries `OwnerPartId` (1) and+`OwnerPartDisplayMode` (its mode). `altium_build_symbol_modes {libraryPath, spec}` builds the+whole set from a `tools/symbol_modes.py` spec — the generator sizes bodies from MEASURED+pin-name lengths (55 mil/char) so names never collide, makes "std" the CCW datasheet wrap+(distinct from 2-column), and draws die-outline modes as centred squares, skipped when the+free middle is under 300 mil. **The editor paints its OWN mode**: `Comp.DisplayMode` changes+data only — switch views with the process `Sch:ToggleComponentDisplayMode`+(`Action=Mode0`/`Next`), which is what `altium_symbol_mode {mode}` does before returning a frame.++**Ralph rule: frame-check EVERY mode, not a sample.** The first LAN7800 shipped with three+defect classes visible only in the modes nobody looked at.++## Footprints: the spec lane++`altium_build_footprint {libraryPath, spec}` consumes a `tools/kicad2altium.py` spec:+- **Via pads**: through-hole pads sharing a designator (16 × "49") all map to that pin — the+ heatsink-via convention; real copper + drill, carried to the board and the drill file.+- **Rounded-rect pads**: `eRoundedRectangular` + `SetState_StackCRPctOnLayer` (KiCad ratio ×100).+- **Layer map**: F.SilkS→Top Overlay, F.Fab→Mechanical 13 "Fab", F.CrtYd→Mechanical 15+ "Courtyard", User.9→Mechanical 9 renamed **"Adom.SolderBalls"** (hidden by default).+- KiCad Y-down is flipped; filled discs are full arcs at r/2 with stroke r.+- Pads must carry a Layer or they are SILENTLY DROPPED (measured); `Bottom*/Mid*` pad size+ properties do not exist in this engine (halt).++## 3D bodies: the canvas rule++The stored lib component is NOT the editing surface. A body added straight to the component+counts once and is GONE after save/reopen — the editor's canvas copy overwrites it. `add_body`+does it right: `PLib.CurrentComponent := Fp` FIRST, then add to the CANVAS (`PLib.Board`).+Canvas-added bodies land 50000 mil from the pads in the stored frame (the canvas origin+offset — this was the CH340G "orange sliver"); `add_body` auto-runs `normalize_bodies` to+centre each body on its pad cluster. Note: the PCB **Library** editor's 3D view does not+render bodies — the board 3D does; prove bodies on a board (`altium_test_board`).++## Variants: N footprints on one component++`altium_add_models {symbolName, models:[{name,pcblib,description}...], params, comment,+description}` attaches N PCBLIB implementations — the user picks the current footprint on the+placed part (Altium's CHANGE PACKAGE). Give every model a description saying what the variant+IS ("with 16 heatsink vias as through pads…"); it shows in the footprint dropdown. Bind each+footprint its own body; on swap the body follows the footprint.++## Parameters: the tri-vendor rule (house rule, John 2026-08-30)++Every component gets `Mouser Part #` (preferred vendor), `DigiKey Part #`, `LCSC Part #`+(exact name → JLCPCB BOM round-trip), plus MPN/Manufacturer/Value/Package. Parameters are+hidden metadata by default (visible ones pile above the symbol — measured);+`altium_set_visibility` shows the one you want (usually Value).++## Pin-1 check (non-negotiable)++Never trust either file. Parse the STEP for the dimple (a small CIRCLE near the top face at a+corner), parse the footprint for pad 1's quadrant, read the bound body back from Altium+(`altium_pcb_report` bbox; `add_body` never rotates). Same quadrant + centred body = AGREE;+report the check per variant, and refuse or rotate on disagreement.++## Library manipulation++`clone_symbol` (all modes carried; deliberately does NOT copy parameters — a reused symbol+must not inherit the source MPN), `rename_symbol`, `remove_symbol`, `read_symbol`,+`copy_footprint` / `xcopy_footprint` (cross-library) / `rename_footprint` /+`remove_footprint`, `cleanup_libs` (kills the factory `Component_1`/`PCBComponent_1` strays).
skills/altium-outputs/SKILL.mdadded+41@@ -0,0 +1,41 @@+---+name: altium-outputs+description: >-+ Checks and manufacturing outputs through the altium-bridge: compile/ERC with the Messages+ panel read back, DRC with violations, OutJob runs (Gerber, ODB++, NC drill, BOM,+ pick-and-place) with the files actually written, and the current caveats (headless STEP+ needs one click, PDF jobs, A365 token gating). Use when running design checks or generating+ fab/assembly outputs from Altium. Trigger words — altium drc, altium erc, compile altium,+ altium gerber, altium outjob, altium bom, pick and place, odb++, nc drill, altium step+ export.+---++# altium-outputs++## Checks++- `altium_compile {projectPath}` (alias `run_erc`) compiles and returns the **Messages panel**+ content (`IDMMessagesManager` read-back) — errors and warnings as data, not a screenshot.+- `altium_run_drc {filePath}` runs the PCB rule check and returns `violations[]`. Trap:+ refocus the board before reading — the DRC report document steals focus and an empty list+ can mean "wrong document", not "clean".++## Outputs++- `altium_run_outjob {outjobPath, medium:"Folder Structure", projectPath}` runs an OutJob+ medium and lists the `files[]` actually written — verify that list, never the return code.+ Proven media on the Bluetooth Sentinel examples: Gerber, ODB++, NC drill, BOM,+ pick-and-place. Shortcuts: `altium_export_gerber` / `altium_export_bom`.+- `ok:true` with an empty files[] means the medium ran against the wrong project or the+ output path differs — read the OutJob's own configured folder.++## Caveats (current, honest)++- **Headless STEP export**: an OutJob with an ExportSTEP output still needs one human click on+ *Generate content*. Two solve lanes are on the plan: a direct process in the 1,111-command+ catalog, or automating that click with the background UIA machinery. Barrett's .NET+ extension verbs (export_step/import_kicad) do not load on AD 26.+- **A365 GraphQL** (`altium_a365_*`): wired and shaped, but needs a token from a workspace+ with Admin → Developer (Pro/Enterprise) — the Develop trial has no such page.+- OutJob→PDF was blocked in the predecessor bridge; on this one run it through+ `altium_run_outjob` and verify the files[] like any medium.
uninstall.sh+1−1@@ -1,7 +1,7 @@ #!/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" "$HOME_DIR/altium-part-import" "$HOME_DIR/altium-kicad-import"+ rm -rf "$HOME_DIR/altium-bridge" "$HOME_DIR/altium-bridge-plan" "$HOME_DIR/altium-part-import" "$HOME_DIR/altium-kicad-import" "$HOME_DIR/altium-install" "$HOME_DIR/altium-libraries" "$HOME_DIR/altium-boards" "$HOME_DIR/altium-outputs" "$HOME_DIR/altium-bridge-dev" "$HOME_DIR/altium-bridge-publish" done rm -rf "$HOME/.local/share/adom/altium-bridge" rm -f "$HOME/.local/bin/altium-dashboard"