← Commit history

EDA Bridge Standard 0.1.0: verbs in tiers from the Altium bridge 0.2.75 catalog, the board model and companion schemas, conventions, coverage of KiCad 1.0.58 and Fusion 360 1.11.44, the generator

Colby Knox ·7ff5d4fd00 ·15d ago ·parent 92b2c8d
16 files changed +16731−1
README.md+84−1
@@ -1,3 +1,86 @@ # EDA Bridge Standard: one way for an AI to drive every PCB tool -The standard every Adom EDA bridge follows so one AI can drive Altium, KiCad, Fusion 360 and any future PCB tool the same way: shared verb names in tiers, the board model JSON, reply and refusal conventions, and a coverage table of each bridge against the Altium reference.+An AI should be able to work on a board the same way whether it lives in Altium Designer, KiCad, Fusion 360+Electronics or the next tool someone writes a bridge for: the same verbs, the same board model, the same checks. This+page is where that is defined and kept in sync.++The **reference is the Altium bridge** ([adom/altium-bridge](https://wiki.adom.inc/adom/altium-bridge)), the most+complete one: its 201 verbs, sorted into tiers, are the standard; its board model is the model. The KiCad+([adom/kicad-bridge](https://wiki.adom.inc/adom/kicad-bridge)) and Fusion 360+([adom/fusion-bridge](https://wiki.adom.inc/adom/fusion-bridge)) bridges are measured against it below.++## What is in the standard++1. **The verbs** (`verbs/standard.json`): every reference verb with its inputs, outputs, hint, pitfalls and an+   example, generated from the Altium bridge's own `altium_describe`, each in a tier:++   | Tier | What it is |+   |---|---|+   | **core** | Read and edit the design: the board model, parts, nets, tracks, vias, pours, symbols, footprints, sheets. Every bridge provides these. |+   | **native** | The tool's own features under one name: DRC, ERC, Gerbers, STEP, BOM, pours, Update PCB, teardrops, net classes, rules, panels. |+   | **checks** | Engineering computed from the board model, not by the tool: impedance, current capacity, fab and assembly limits, return paths, copper balance, 3D heights, schematic review, placement review, pair routing, length tuning, designators, via fences, the board report. One shared engine. |+   | **app** | Driving the application: launch, licence, dialogs, windows, views. Each bridge has its own; the names are shared. |+   | **platform** | One tool's platform only (Altium 365, OutJobs, integrated libraries). Kept out of the standard, listed so nothing is lost. |++2. **The board model** (`schema/board.schema.json`, [docs/board-model.md](docs/board-model.md)): one PCB as JSON,+   millimetres, KiCad's copper layer names on every tool, tracks, arcs, vias, pads, parts, pours, the outline with its+   arcs, and a revision that guards every edit. The companion models (stack-up, poured copper, 3D bodies, sheet parts+   with pin types, netlist) are `schema/companions.schema.json`.++3. **The conventions** ([docs/conventions.md](docs/conventions.md)): verb names, units and frames, the reply envelope+   (`success`, `errorCode`, `error`, `_hint`), revision guards, dry-run plans, refusal codes, and how a bridge treats the+   user's running tool (background first, no dialog left for the user, never a killed tool).++Fab limits are not kept here: they come from [adom/pcb-design-rules](https://wiki.adom.inc/adom/pcb-design-rules), the+canonical record per vendor.++## Coverage today++How many of the standard's verbs each bridge has (same job, same or equivalent name; partial counts). The full table,+verb by verb: [docs/coverage.md](docs/coverage.md).++<!-- coverage:start (tools/generate.py writes this) -->+| Tier | Verbs | Altium | KiCad | Fusion 360 |+|---|---|---|---|---|+| **core** | 60 | 60 | 12 | 9 |+| **native** | 34 | 34 | 18 | 10 |+| **checks** | 32 | 32 | 4 | 1 |+| **app** | 56 | 56 | 21 | 19 |+| **platform** | 19 | 19 | 0 | 0 |+| standard (all but platform) | 182 | 182 | 55 | 39 |+<!-- coverage:end -->++The **checks** tier is where the other bridges are furthest behind, and it is the cheapest to close: those verbs are+computed from the board model, not by the tool, so a bridge that returns the board model can run them all from one+shared engine. Most of the rest is not missing from KiCad or Fusion themselves (both have DRC, ERC, pours, Gerbers;+KiCad has length tuning, a pair router and teardrops of its own): their bridges do not expose them yet.++## For a bridge author++1. Return the board model from `<prefix>_routing_state` exactly as `schema/board.schema.json` says (and the companion+   models from their verbs).+2. Name verbs from `verbs/standard.json`; where the tool has the same job under another name, add the standard name as+   an alias.+3. Keep the reply conventions in [docs/conventions.md](docs/conventions.md).+4. Check yourself against `docs/coverage.md` and add your bridge's verb list to `reference/` so it is measured.++## Keeping it in sync++`reference/` holds each bridge's verb list at a version (the reference bridge's full `describe`). To refresh:++```bash+# the reference bridge's catalog, from a machine running it+adom-bridge altium_describe '{}' > reference/altium-<version>.json+# then regenerate the standard, the coverage table and this page's numbers+python3 tools/generate.py+```++`tools/tiers.json` puts each reference verb in a tier; `tools/equivalents.json` records verbs the other bridges have+under another name. When the reference bridge gains a verb, give it a tier and regenerate.++## Status++Version 0.1: the verbs, the board model and the conventions, with Altium bridge 0.2.75 as the reference. Next: the+checks tier as a shared library and command-line tool every bridge can call, and fixture boards (the same small board+as `.PcbDoc`, `.kicad_pcb` and Fusion) with the expected board model and check results, so a bridge can prove it+conforms.
SKILL.mdadded+54
@@ -0,0 +1,54 @@+---+name: eda-bridge-standard+public: true+description: >-+  The standard every Adom EDA bridge follows so one AI can drive Altium, KiCad, Fusion 360 and any future PCB tool the+  same way: the shared verb names in tiers (core, native, checks, app, platform), the board model JSON every+  <prefix>_routing_state returns (mm, KiCad copper layer names, revision guard), the reply and refusal conventions, and+  how far each bridge covers it. Altium bridge is the reference. Use when building or extending an EDA bridge, adding a+  verb to kicad/fusion/altium bridges, porting a check between bridges, comparing what each bridge can do, or reading a+  board model from any bridge. Trigger words: eda bridge standard, bridge parity, same verbs every eda tool, board model+  json, routing_state schema, port altium verb to kicad, what can the kicad bridge not do, standard verb names.+---++# eda-bridge-standard++The standard for Adom EDA bridges. The Altium bridge is the reference: its verbs are the standard and its board model+is the model.++## Read first++| File | What it holds |+|---|---|+| `verbs/standard.json` | every standard verb: name (no prefix), tier, summary, input, output, hint, pitfalls, related, example |+| `docs/coverage.md` | each verb on Altium, KiCad and Fusion 360: exact, partial or missing |+| `schema/board.schema.json` | the board model (`<prefix>_routing_state`) |+| `schema/companions.schema.json` | stack-up, poured copper, 3D bodies, sheet parts with pin types, netlist |+| `docs/board-model.md` | the board model's rules, with the reasons |+| `docs/conventions.md` | names, units, the reply envelope, refusal codes, how to treat the user's tool |++## When you add a verb to a bridge++1. Look the job up in `verbs/standard.json` and use that name with your prefix (`kicad_return_path`), its inputs and+   its output keys. If the standard does not have it, add it to the reference bridge first, or propose it on this page.+2. If your tool does the job under its own verb already, keep it and add the standard name as an alias.+3. Reply with `success`, `errorCode` and `error` on refusal, and a `_hint` that says the next step. Take+   `expectedRevision` on edits; offer `dryRun` where a plan can be wrong.+4. Add the bridge's verb list to `reference/` and run `python3 tools/generate.py`: `docs/coverage.md` moves.++## When you port a check (the checks tier)++The checks are computed from the board model and its companions, not by the tool. A bridge that returns the board+model exactly as the schema says can run them; the Altium bridge's Rust is the reference implementation+(`bridge-rs/src/` in adom/altium-bridge: `impedance.rs`, `current.rs`, `fab.rs`, `assembly.rs`, `coppermap.rs`,+`bodies.rs`, `placement.rs`, `schreview.rs`, `pairroute.rs`, `lengthtune.rs`, `silkplace.rs`, `panel.rs`, `report.rs`).+Planners (pair routes, serpentines, designators, via fences, thermal vias, panels) answer geometry; each bridge applies it+with its own core verbs (`route_net`, `place_vias`, `apply_silkscreen`).++## Rules that bite++- Millimetres, the tool's frame, y up, absolute: what `routing_state` reports is what `route_net` takes.+- Copper layers as `F.Cu`, `In<n>.Cu`, `B.Cu`, `*.Cu` on every tool.+- A custom-shape pad is reported as its bounding rectangle, never 0 x 0.+- Net-less copper (a hatched pour's pieces) stays in the model with an empty net.+- Fab limits come from adom/pcb-design-rules, with the vendor page and date in the reply.
docs/board-model.mdadded+54
@@ -0,0 +1,54 @@+# The board model++One PCB, the same shape from every EDA tool. Every check in the standard reads it; every bridge returns it from+`<prefix>_routing_state`. The JSON Schema is `schema/board.schema.json`; the companion models (stack-up, poured copper,+3D bodies, sheets, netlist) are `schema/companions.schema.json`.++The reference is the Altium bridge. AI Flow reads the same shape from KiCad and Fusion, which is why it uses KiCad's+layer names.++## In one picture++```json+{+  "units": "mm", "boardId": "C:/proj/Board.PcbDoc", "revision": "476053b2ec164071",+  "nets": ["GND", "N1"], "unconnected": 0,+  "tracks": [{"start": {"x": 12.7, "y": 11.905}, "end": {"x": 26.3, "y": 11.905}, "width": 0.3, "layer": "F.Cu", "net": "N1"}],+  "arcs": [{"center": {"x": 5, "y": 5}, "radius": 1, "startAngle": 0, "endAngle": 90, "width": 0.25, "layer": "F.Cu", "net": "N1"}],+  "vias": [{"position": {"x": 7.5, "y": 2.5}, "size": 0.6, "drill": 0.3, "layers": ["F.Cu", "B.Cu"], "net": "GND"}],+  "pads": [{"ref": "U1", "name": "8", "x": 3.3, "y": 11.905, "sizeX": 1.5, "sizeY": 0.6, "rotation": 0, "layer": "F.Cu", "drill": 0, "net": "N1", "shape": "rect"}],+  "components": [{"ref": "U1", "x": 6, "y": 10, "rotation": 0, "side": "top", "pattern": "SOIC8", "has3d": true}],+  "polygons": [{"net": "GND", "layer": "B.Cu", "points": [[0.5, 0.5], [20, 0.5], [20, 19.5], [0.5, 19.5]]}],+  "outline": [[1, 0], [39, 0], [40, 1], [40, 19], [39, 20], [1, 20], [0, 19], [0, 1]],+  "outlineEdges": [{"kind": "line", "start": [1, 0], "end": [39, 0]}, {"kind": "arc", "start": [39, 0], "end": [40, 1], "center": [39, 1], "radius": 1, "angle1": 270, "angle2": 360}]+}+```++## Rules a bridge must keep++- **Millimetres, the tool's frame, y up, absolute.** No offset from the board origin: the checks compare against the+  outline the same reply gives.+- **Copper layers by KiCad's names** (`F.Cu`, `In<n>.Cu`, `B.Cu`, `*.Cu`). Non-copper layers may use the tool's own+  name or id (Altium sends `L57` for Mechanical 1).+- **A custom-shape pad is its bounding rectangle** (`shape: "rect"`, rotation 0). Altium's API gives such a pad no+  size; reported as 0 x 0, every clearance check walked straight through the Bluetooth Sentinel's 5 mm coin cell contacts.+- **Net-less copper is reported with an empty net**, not dropped: a hatched pour's pieces arrive that way from Altium,+  and the copper checks count them.+- **`unconnected` is computed from the copper**, not taken from the tool's ratsnest (Altium's ratsnest skips parts+  outside the board). A bridge may add the tool's own count under its own key (`altiumRatsnest`).+- **`revision` changes when copper, parts or the outline change**, and only then: it is the edit verbs' guard.+- **The outline as edges, arcs kept.** Measuring against a curved edge needs the arc; sample it every 2 degrees (16+  points per arc put copper 0.04 mm outside the Sentinel's round edge).+- **`polygons` is the pour as drawn.** The copper actually poured (regions minus holes) is the pours model+  (`pour_state`): checks that need real copper read that.++## The companion models++| Model | Verb | What reads it |+|---|---|---|+| Stack-up | `layer_stack` | impedance, current capacity, fab check (copper weight, thickness), copper balance, return paths (planes) |+| Poured copper | `pour_state` | copper balance, return paths and their fix plans |+| 3D bodies | `bodies` (Altium: an agent call behind `height_check`) | height check, placement check |+| Sheet parts with pin types | `read_sheet_file` (Altium reads the .SchDoc file itself) | schematic review, pin-to-pad check |+| Netlist | `netlist` | schematic review |+| Silkscreen | `silk_state` | designator placement, fab check (text height, line width) |
docs/conventions.mdadded+90
@@ -0,0 +1,90 @@+# Conventions every bridge follows++These are the rules the reference bridge (Altium) already keeps. A bridge that keeps them lets one AI drive every EDA tool+the same way, and lets the shared checks run on any of them.++## Names++- A verb is `<prefix>_<name>`: `altium_route_net`, `kicad_route_net`, `fusion_route_net`. The `<name>` comes from+  `verbs/standard.json`. A tool that does the same job under its own verb keeps it and adds the standard name as an+  alias, so `docs/coverage.md` can list it as exact.+- A verb a tool cannot do at all is not faked. Leave it out, or answer `success:false` with+  `errorCode: "not_supported"` and an `error` that says why.+- Tool-only verbs (Altium 365, OutJobs) keep their own names under the `platform` tier and stay out of the standard.++## Units, layers, frames++- Millimetres everywhere in replies and arguments; degrees for angles, counterclockwise from +x. An argument in other+  units says so in its name (`copperUm`, `wMil`).+- Coordinates are the tool's own board frame, y up, absolute: what `routing_state` reports is what `route_net` takes.+- Copper layers carry KiCad's names on every tool: `F.Cu`, `In1.Cu` to `In30.Cu`, `B.Cu`, and `*.Cu` for a pad on every+  copper layer. A layer argument also takes the tool's own id.+- Nets are named as the design names them; matching is case-insensitive.++## Replies++Every reply is one JSON object:++| Key | Meaning |+|---|---|+| `success` | true when the verb did what it says. A check that ran and found problems is `success: true` with its findings; `ok` (or `fits`, `ready`) carries the verdict |+| `errorCode` | on `success:false`: a short stable code from the table below |+| `error` | on `success:false`: one sentence a person can read, naming the thing (the net, the pad, the file) |+| `_hint` | what to do next, in words. The AI reads replies, not skill files: the hint is where the next step is taught |+| `related` / `_next` | verbs that usually follow |+| `revision` | after an edit: the board's new revision (pass it back as `expectedRevision`) |+| `bridgeVersion` | the bridge's version |+| `windowEtiquette` | `background` when the verb took no focus from the user |++Verbs that change a design:++- Take `expectedRevision` and refuse with `stale_board` when the board changed since the caller read it.+- Land as one undo step in the tool.+- Read back what they wrote and report it: positions to 0.001 mm, rule descriptors from the tool, poured area.+- Plan before acting when the plan can be wrong: `dryRun: true` answers the plan (points, sizes, what it replaces) and+  changes nothing. Planners that can short something check the plan first and refuse with the problem and the planned+  geometry (`pair_plan_blocked`), never place it.+- Never save over a file without being told (`overwrite: true`), and never answer a save prompt for the user.++Checks:++- Name each finding with where it is (`at: [x, y]`, a designator, a pad `U1.3`, a net).+- Separate errors from warnings, and say when a check could not see something (`noBody`, `poursWithoutRegions`,+  `complete: false`) rather than passing it.+- Name the source of any outside number (a fab's limit: its page and the date it was read). Fab limits come from+  [adom/pcb-design-rules](https://wiki.adom.inc/adom/pcb-design-rules), the canonical record per vendor.++## Refusal codes++The common ones. A bridge adds its own for its tool's states, in the same snake_case form.++| Code | Meaning |+|---|---|+| `bad_args` | a required argument is missing or out of range: `error` says which |+| `not_supported` | this tool cannot do this verb |+| `app_not_running` / `<tool>_not_running` | start the tool first (`launch`) |+| `not_licensed` | the tool runs without a licence that allows scripting |+| `no_current_board` / `no_board` | no board document is open: open one or pass `filePath` |+| `wrong_board` / `board_mismatch` | the open board is not the one named |+| `stale_board` | `expectedRevision` no longer matches: read the board again |+| `no_such_net` / `no_such_pad` / `part_not_found` / `designator_not_found` | the named thing is not on the board |+| `no_outline` | the board has no outline |+| `not_routed` | the net has no routed copper |+| `blocked_by_dialog` | a dialog in the tool blocks the verb: `dialogs` names it |+| `project_not_found` / `not_a_schdoc` / `unsupported_document` | the file is not what the verb needs |+| `write_failed` | the bridge could not write a file it was asked to |+| `nothing_to_do` | the request asked for no change |+| `*_plan_blocked`, `no_room`, `outline_on_mechanical_layer` | a planner refusing a plan that would be wrong, with why |++## Driving the user's tool++- Background first: verbs work without taking the window or the mouse. A verb that must take focus says so in its+  docs and gives the user's window back after.+- A dialog the user would have to dismiss is a bridge defect: pre-empt it, answer it only when the answer carries no+  decision (a lone OK), keep a picture of anything answered without being read, and never answer a save prompt.+- Never kill the tool when it can be closed: a killed EDA tool can strand a licence seat.++## Every verb documents itself++`<prefix>_describe` returns every verb with `summary`, `input`, `output`, `hint`, `pitfalls`, `related`, `example` and+`timeoutSeconds`. `verbs/standard.json` is generated from the reference bridge's `describe`.
docs/coverage.mdadded+250
@@ -0,0 +1,250 @@+# Coverage: every standard verb on each bridge++Reference: Altium bridge 0.2.75. Compared: KiCad bridge 1.0.58, Fusion 360 bridge 1.11.44.+`exact` is the same job under the same or an equivalent name; `partial` does part of it or does it differently; blank is missing.+Generated by `tools/generate.py` from `verbs/coverage.json`; do not edit by hand.++| Tier | Verbs | Altium | KiCad | Fusion 360 |+|---|---|---|---|---|+| **core** | 60 | 60 | 12 | 9 |+| **native** | 34 | 34 | 18 | 10 |+| **checks** | 32 | 32 | 4 | 1 |+| **app** | 56 | 56 | 21 | 19 |+| **platform** | 19 | 19 | 0 | 0 |+| standard (all but platform) | 182 | 182 | 55 | 39 |++## core++Read and edit the design. Every bridge provides these: they are how an AI sees and changes a board.++| Verb | What it does | KiCad | Fusion 360 |+|---|---|---|---|+| `add_body` | Put a 3D body on a footprint of a PcbLib: a STEP model, or an extruded box when no step is given. It REPLACES the footprint's existing 3D bodies (a... |  |  |+| `add_models` | Attach one or more PCBLIB footprint models, parameters, comment and description to a SchLib component (Altium's several-footprints-per-symbol mechanism). |  |  |+| `add_symbol_art` | Draw the chip-outline artwork (the chip-thumbnailer polylines adom-symbol baked into the KiCad symbol) into a SchLib component, fitted to its body... |  |  |+| `apply_silkscreen` | Land a silkscreen plan in one undo step: designators and comments moved, sized and turned (or hidden), free overlay texts and lines added (bottom texts... |  |  |+| `board_outline` | Draw the board shape as tracks and arcs on a mechanical layer: the outline a fab house reads from the Gerbers. A board built by script has its shape but... |  |  |+| `board_shape` | Redefine the CURRENT board's outline as a wMm x hMm rectangle at (x0Mm, y0Mm) by rewriting the board outline's segments. |  |  |+| `board_summary` | One-screen summary of the current board, shaped from one read_board round trip: component counts by side, nets, copper layers actually in use, rule counts. |  | `fusion_board_info` (partial) |+| `clone_symbol` | Copy a symbol inside the current SchLib under a new name, all display modes included. |  |  |+| `component_data` | Placement data for one board component, or for all of them: footprint pattern, comment, layer/side, position, rotation, size, pad count and the nets on its... |  |  |+| `component_pins` | Every pad of one board component with its net, position, layer, size and hole. |  |  |+| `copper_layers` | Every copper layer Altium can address on the current board (top, mid 1-30, planes 1-16, bottom) with names and visibility. |  |  |+| `copy_footprint` | Duplicate a footprint inside the current PcbLib under a new name (every primitive replicated). |  |  |+| `create_footprint` | Create one footprint from a pad list in a PcbLib (the library is created and saved if the path does not exist) and return a proof frame. |  |  |+| `create_symbol` | Create one rectangular symbol from a pin list in a SchLib (the library is created and saved if the path does not exist) and return a proof frame. |  |  |+| `doc_params` | Read or set a sheet's document parameters: the title block (Title, DocumentNumber, Revision, DrawnBy, Author, CompanyName, ...). |  |  |+| `find_symbol` | Find symbols (or footprints with kind:'footprint') by part of their name across libraries: the given ones, or every installed .SchLib/.PcbLib. |  |  |+| `footprint_primitives` | Geometry dump of one PLACED component: its tracks, arcs, pads (with solder and paste mask expansion in um) and texts in mils with layer ids; other primitive... |  |  |+| `layer_stack` | The CURRENT board's stack-up: copper layers in order with copper thickness and the dielectric under each (height, constant, material), and the total... |  | `fusion_board_stackup` |+| `layers` | The current board's layer set with ids, names, kind and whether each is displayed. |  |  |+| `layers_in_use` | Which layers of the current board actually carry primitives: copper layers with copper (densest first) and every layer with anything on it. |  |  |+| `list_designators` | Every component designator on the current board. |  |  |+| `list_library` | Names of every component in a .SchLib or every footprint in a .PcbLib (kind taken from the extension). Opens the library; no screenshot. | `kicad_list_footprints / kicad_list_symbols` (partial) |  |+| `list_nets` | Every net on the current board with its pad count. |  |  |+| `move_parts` | Move and rotate placed components; each part is read back to 0.001 mm and the exact rotation. One undo step. | `kicad_move_footprint` | `fusion_move_parts` |+| `net_connections` | Every pad on the board with its net and owner (Designator.Pad), plus a bounding span and the airline length (minimum spanning tree over its pads, airlineMm)... | `kicad_trace_net / kicad_find_connections` (partial) |  |+| `normalize_bodies` | Move every 3D body of a PcbLib's footprints (or one footprint) onto its pad cluster. A body added through the canvas lands 50000 mil from the pads in the... |  |  |+| `part_parameters` | The parameter names a project's parts carry (with how many parts carry each), or with name, that parameter's value on every part. |  |  |+| `place_on_pcb` | Place ONE footprint from a PcbLib on a new board and return a proof frame. The single-part predecessor of altium_test_board. | `kicad_place_footprint` |  |+| `place_on_sch` | Place one symbol, or a ';' list of symbols, from the open SchLib on ONE new sheet (sheet sized FROM the measured parts: 500 mil gaps, 600 mil margins, A4... |  |  |+| `place_vias` | Place vias (through, top to bottom). One undo step, read back. | `kicad_add_via` | `fusion_place_vias` |+| `plate_vias` | Make every drilled pad in a PcbLib a plated MULTI-LAYER pad (all footprints, or footprintName). adom-lbr writes heatsink vias as pads with a hole; Altium... |  |  |+| `read_board` | ONE round trip: components, nets, layers, copper, rules, occupancy, outline. |  | `fusion_board_info / fusion_get_design_info` (partial) |+| `read_rules` | Every design rule on the current board, flat, disabled ones included: name, kind ordinal, enabled, priority, both scope expressions, comment, and Altium's... | `kicad_list_design_rules` |  |+| `read_schematic` | Components on the CURRENT schematic sheet (designator, library reference, comment, position, parameters) in one agent round trip. |  | `fusion_schematic` (partial) |+| `read_symbol` | Read a SchLib component back: description, display-mode count, the pins of mode 0 (designator, name, electrical type), every parameter with its hidden flag,... |  |  |+| `remove_footprint` | Delete one footprint from the current PcbLib. |  |  |+| `remove_items` | Delete specific free tracks and vias, given exactly as altium_routing_state returned them. One undo step. | `kicad_remove_route` (partial) |  |+| `remove_symbol` | Delete one component from the current SchLib. |  |  |+| `rename_footprint` | Rename a footprint in the current PcbLib. |  |  |+| `rename_symbol` | Rename a component (LibReference) in the current SchLib. |  |  |+| `ripup_nets` | Delete every free track, arc and via on each named net. One undo step. |  | `fusion_ripup_nets` |+| `route_net` | Route one net as tracks, segment by segment; a waypoint on another layer drops a via there. One undo step, read back. | `kicad_route_net` | `fusion_route_net` |+| `routing_state` | The WHOLE current board as data, in mm: tracks, arcs, vias, pads (with owner and net), components (ref, x, y, rotation, side), polygon pours, the outline,... | `kicad_routing_state` | `fusion_routing_state` |+| `rules_by_kind` | The current board's ENABLED design rules grouped by rule kind (Clearance, Width, RoutingVias, ...), each group sorted by priority. Shaped from one... |  |  |+| `sch_connect` | Connect pins of placed parts: a wire stub from each pin's outer end plus a net label (kind label), a power port (kind power: GND-like nets get a ground... | `kicad_sch_wire / kicad_sch_label` (partial) |  |+| `sch_edit` | Change placed parts on a sheet: move (to x,y mil), moveBy (dx,dy), rotate (degrees), rename (designator), re-comment (comment), delete. One undo step. | `kicad_sch_set_property / kicad_sch_move / kicad_sch_delete` (partial) |  |+| `sch_place_part` | Place a component from a .SchLib onto an EXISTING sheet at a position, with its library parameters (MPN, Manufacturer, distributor numbers) and its... | `kicad_sch_place_symbol` |  |+| `sch_sheet_symbol` | Make a design hierarchical: a sheet symbol on the parent sheet for a child sheet, its entries joined to the parent's nets, and matching ports on the child.... |  |  |+| `sch_swap_part` | Replace a placed part with another library part (a different value, package or symbol) at the same place, designator and rotation, keeping its connections:... |  |  |+| `schematic_data` | Components on a schematic sheet (filePath, else the CURRENT sheet) with their pins (number, name, electrical type), position and orientation. designator... |  |  |+| `set_models` | Replace every footprint model on a SchLib component with ONE PCBLIB model (set_params footprint and add_models APPEND, which is how a symbol ends up with two). |  |  |+| `set_outline` | Reshape the current board's outline (a rectangle with rounded corners, or a polygon). One undo step. |  |  |+| `set_params` | Set parameters on a SchLib component, optionally its comment, and optionally link a footprint by name. |  |  |+| `set_track_width` | Set the width of the EXISTING tracks and arcs on nets or a net class (rules only steer new routing). Widen power nets after routing, or thin a net that... |  |  |+| `set_visibility` | Show or hide one named parameter on a SchLib component. |  |  |+| `sheet_params` | Write parameters onto PLACED parts on the schematic sheets (the BOM write-back): MPN, Manufacturer, Mouser/DigiKey/LCSC numbers, anything. A missing... |  |  |+| `silk_state` | The silkscreen as it stands: every part's body box, its designator and comment (text, location, size, rotation, shown, Altium's measured box), and the free... |  |  |+| `swap_footprint` | Replace the FIRST component on the current board with a footprint from a PcbLib at the same position, re-assigning nets by pad designator. Tracks are... |  |  |+| `symbol_prims` | Read a SchLib component's drawing: primitive counts by kind, body rectangle, polyline bounding box, display mode and the footprint models. Read-only, use it... |  |  |+| `xcopy_footprint` | Copy a footprint ACROSS libraries: from fromLib into toLib (opened and made current), optionally under a new name. |  |  |++## native++Wraps the EDA tool's own feature (its DRC, ERC, Gerbers, pours, Update PCB). Every tool has one; the bridge exposes it under this name.++| Verb | What it does | KiCad | Fusion 360 |+|---|---|---|---|+| `add_rule` | Add one SCOPED design rule that set_design_rules' board-wide values cannot express: a net's or class's clearance or width, a length limit, a matched-length... |  |  |+| `annotate` | Number every '?' designator (R?, C?, U?) across the project's sheets: the lowest free number per prefix, top-left first on each sheet. dryRun shows the plan. |  |  |+| `apply_pours` | Add solid polygon pours from outlines and pour them, each with its own connect style (solid joints or thermal spokes), plus pour keepouts (polygon cutouts);... | `kicad_add_zone / kicad_apply_zone_plan` | `fusion_apply_pours` |+| `autoroute` | Altium's own autorouter (Situs) on the current board: picks a strategy, keeps existing routing locked by default, routes, and answers the unconnected count... | `kicad_autoroute` |  |+| `build_symbol_modes` | Build ONE SchLib component with N display modes (Normal + Alternate 1..N-1) from a symbol_modes spec file on this machine, and return a proof frame. |  |  |+| `cleanup_libs` | Remove the factory-default stray every fresh library carries: Component_1 from the current SchLib and an empty (no pads) PCBComponent_1 from the current PcbLib. |  |  |+| `compile` | Compile a project and return Altium's Messages panel (ERC + connectivity) with counts by class. With no path it compiles Altium's bundled example project. |  |  |+| `component_classes` | List, make or remove component classes on the board, each optionally with a room: a placement area that keeps its parts together (fitted around the parts or... |  |  |+| `diff_pairs` | Differential pairs on the current board: list them, define one from two nets (replacing a pair of that name), or remove one. Altium's differential-pair... |  |  |+| `export_bom` | Bill of materials export: altium_run_outjob with the assembly OutJob and the 'Folder Structure' medium as defaults. | `kicad_export_bom_csv` (partial) | `fusion_export_bom` |+| `export_gerber` | Gerber/drill export: altium_run_outjob with the fabrication OutJob and the 'Folder Structure' medium as defaults. | `kicad_export_gerber` | `fusion_export_gerbers` |+| `export_manufacturing` | A project's manufacturing set in one call: Gerbers, NC drill, pick and place (CSV, mm) and the BOM, from an OutJob the bridge writes beside the project... | `kicad_export_gerber / kicad_export_pos` (partial) | `fusion_export_gerbers / fusion_export_cpl / fusion_export_bom` (partial) |+| `export_pdf` | Every sheet of a project as one PDF, plus on request the board's composite print, Altium's assembly drawings (top and bottom, designators on the parts) and... | `kicad_export_pdf` |  |+| `export_step` | Headless STEP export of a board. Default lane 'outjob': Altium's own ExportSTEP output run from a throwaway project in %TEMP% that points at the board - no... | `kicad_export_step` | `fusion_export_step` |+| `install_library` | Register a library file in Altium's Installed Libraries (IntegratedLibraryManager.InstallLibrary) so its parts appear in the Components panel. | `kicad_install_library` |  |+| `install_library_bundle` | One-click install of a wiki library bundle (zip of .SchLib/.PcbLib): unpack, register, open the library, prove it with a frame. | `kicad_install_library_bundle` | `fusion_install_library_bundle` |+| `list_installed_libraries` | Every library in Altium's Installed Libraries list (read-only). |  |  |+| `net_classes` | Net classes on the current board: list them with their nets, or make one (replacing a class of that name) with an optional width and clearance rule scoped... |  |  |+| `netlist` | The compiled netlist of a project: every net with its pins (Designator.Pin) across every sheet, the hierarchy resolved; or one net; or compare with the nets... | `kicad_extract_netlist` |  |+| `panelize` | Step and repeat a board into a manufacturing panel: a new .PcbDoc holding Altium's own embedded board array of the board (rows x cols), with rails, the... |  |  |+| `pcb_report` | Numbers instead of pixels: for a PcbLib, every footprint's pads, pad bounding box, primitive census and 3D bodies; for a board, outline, components with... | `kicad_errors` (partial) |  |+| `pour_state` | Every polygon pour with the copper it actually poured: filled area per pour and per layer, and the pours that poured nothing. | `kicad_zone_state` | `fusion_pour_state` |+| `remove_pours` | Delete polygon pours by net (and layer), or all of them, and the pour keepouts (cutouts) apply_pours made. Tracks and vias are untouched. | `kicad_remove_zone` | `fusion_remove_pours` |+| `replicate_layout` | Copy one channel's placement to matching channels: each target keeps its anchor part where it is and every other part takes the source's offset and rotation... |  |  |+| `repour` | Repour every polygon on the board (Altium's Repour All) and answer the copper after. Runs outside the script engine so scoped rules apply. |  |  |+| `routing_drc` | Design-rule violations on the current board WITH locations, plus the unrouted (ratsnest) count, as one clean/not-clean answer. | `kicad_routing_validate` (partial) | `fusion_routing_drc` |+| `run_drc` | Run the design rule check on a board and read the violation markers back. PCB:DesignRuleCheck opens a MODAL; the bridge clicks 'Run Design Rule Check'... | `kicad_run_drc` |  |+| `run_erc` | Electrical rules check. Same handler as altium_compile: in Altium, compiling the project IS the ERC. | `kicad_run_erc` |  |+| `set_design_rules` | Write a fab's design rules into the current board: clearance, track width, routing via style, hole size, annular ring, hole-to-hole and board-edge clearance... | `kicad_set_design_rules` | `fusion_set_design_rules` |+| `silkscreen_prep` | Clean the silkscreen off the copper with Altium's own Tools > Silkscreen Preparation: silk clipped clear of every mask opening (pad) by the board's silk... |  |  |+| `symbol_mode` | Show one display mode (0 = Normal, 1.. = alternates) of the CURRENT SchLib component in the editor and capture a frame. Drives the editor's own mode toggle,... |  |  |+| `teardrops` | Add (or remove) teardrops with Altium's own Tools > Teardrops: on vias and through-hole pads, SMD pads, track width changes and T-junctions, for the whole... |  |  |+| `uninstall_library` | Remove libraries from Altium's Installed Libraries: the inverse of install_library / install_library_bundle. Library files are never touched. | `kicad_uninstall_library` |  |+| `update_pcb` | Design > Update PCB Document from the schematic (the engineering change order): parts added, removed or renamed, nets and footprints updated on the board.... |  |  |++## checks++Engineering computed from the board model, not by the tool: one shared engine, so every bridge that serves the model gets them all.++| Verb | What it does | KiCad | Fusion 360 |+|---|---|---|---|+| `assembly_check` | Ready for JLCPCB assembly? The board against both JLCPCB PCBA tiers as published (Economic and Standard): board or panel size, thickness and layers, parts... |  |  |+| `auto_classes` | Net classes from the nets' own names: GND, Power (VCC, VDD, 3V3, VBAT...), Clock, HighSpeed (USB, Ethernet, HDMI, PCIe, DDR, MIPI, LVDS, SATA...) and the... |  |  |+| `board_features` | Add the furniture every real board needs in one undo step: mounting holes (at points or the four corners), fiducials, keepout areas, and a stitching-via... |  |  |+| `board_report` | One shareable page per board: every check run against the live board and its project (Altium's DRC, JLCPCB fab limits and assembly tiers, the schematic... |  |  |+| `bom` | The project's bill of materials read straight from Altium: every schematic sheet the .PrjPcb lists (placed parts and their parameters) merged with the board... |  |  |+| `build_board` | Build a NEW board from a parts list and a netlist: outline (rounded corners allowed), every footprint from one PcbLib placed by its own origin, pad nets... |  |  |+| `build_footprint` | Build a complete footprint (pads, silkscreen, courtyard, arcs) in a PcbLib from a kicad2altium spec file on this machine, and return a proof frame. |  |  |+| `build_schematic` | Draw a design's schematic from its netlist with the project library's symbols, inside a new project (.PrjPcb with the board and libraries), compile it, and... |  | `fusion_schematic_plan / fusion_schematic_run` (partial) |+| `check_orientation` | Advisory: 2-pad parts whose rotation could be better. Each orthogonal rotation is scored in place by airline length (a routable net's MST with the pad... |  |  |+| `copper_balance` | How much of each copper layer is copper (tracks, pads, vias and what Altium actually poured, inside the outline; a plane counts full), and how far apart... |  |  |+| `cross_check` | Compare one schematic sheet's designators with a board's. Altium holds ONE current document, so the verb activates the board, reads it, then activates the... | `kicad_analyze_connections` (partial) |  |+| `current_capacity` | Will this trace carry the current? For each power net and its current: the narrowest track on every layer it is routed on against the width IPC-2221 needs... |  |  |+| `fab_check` | Before ordering: the board's fab-relevant minimums measured from the live board (narrowest track, smallest via drill, diameter and annular ring, smallest... |  |  |+| `find_space` | Free spots on the board for a part near another (clear of every other part on that side, of that side's tracks and vias, and inside the outline), nearest... |  |  |+| `footprint_from_land_pattern` | Build a complete footprint from the land pattern a datasheet prints: pads numbered the standard way (chip, dual SOIC/TSSOP/DFN, SOT, quad QFP/QFN, BGA), an... |  |  |+| `height_check` | Will it fit the enclosure? Each part's height from its 3D bodies (top and bottom side), the parts over a height limit board-wide or under a lid feature in a... |  |  |+| `impedance` | Controlled impedance from the board's own stack-up: the track width each copper layer needs for a single-ended target (50 ohm default) and the width and gap... |  |  |+| `import_part` | One call imports a whole part: footprint spec + STEP + symbol modes + models + params + save. |  |  |+| `jlcpcb_order` | A JLCPCB order in one call: the board checked against JLCPCB's fab limits, the Gerbers and drill zipped the way JLCPCB's order page takes them, the JLCPCB... |  |  |+| `length_tune` | Lengthen a routed net to a target length, or to match another net (a pair's skew, a DDR byte lane): a serpentine in its longest straight track that has room... |  |  |+| `net_lengths` | Routed length of every net (or the ones named) on the live board, by layer with its vias; the skew inside each differential pair; and the spread of... |  |  |+| `pin_pad_check` | Do the footprints fit their symbols? Part by part, the symbol's pin numbers (from the sheet files) against the footprint's pad names (from the board): pins... |  |  |+| `placement_check` | Review the placement before routing: parts whose courtyards (pads and body, grown by a margin) overlap on the same side, parts off the board or over its... | `kicad_placement_validate` (partial) |  |+| `read_sheet_file` | Read a schematic sheet straight from its .SchDoc file, without Altium (it need not be installed or running): every part with its designator, library... |  |  |+| `return_path` | Where signal tracks lose their reference: for every signal net, the length of track on each layer that runs over no pour or plane copper on either... |  |  |+| `review_board` | Design-review checks (14) over one board read; schematic checks with includeSchematic. | `kicad_lint_board` (partial) |  |+| `route_pair` | Route a differential pair as a pair: one centreline from the source pads' midpoint through waypoints to the destination pads' midpoint, each net offset to... |  |  |+| `schematic_review` | Review the schematic before Update PCB: every placed pin (read from the sheet files, with its electrical type) against Altium's compiled netlist. Errors:... | `kicad_lint_schematic` (partial) |  |+| `silk_designators` | Place every reference designator the way assembly houses want: one size, upright, beside its own part, clear of every pad (JLCPCB: 0.15 mm), every other... |  |  |+| `test_board` | Place one footprint, or a ';' list, from the open PcbLib on ONE board: a NEW board (wMm x hMm outline) when pcbPath is new or omitted, APPENDED to the... |  |  |+| `thermal_vias` | A thermal via grid in a part's exposed pad (the ground or thermal pad under a QFN, DFN, PowerPAD or regulator): centred, inset from the pad's edge, sized by... |  |  |+| `via_fence` | A via fence for EMC: a row of ground vias on both sides of RF or fast nets' tracks, and/or round the board edge, at a pitch from the frequency (lambda/20)... |  |  |++## app++Driving the application itself (launch, licence, dialogs, windows, views). Each bridge has its own; the names are shared where the job is.++| Verb | What it does | KiCad | Fusion 360 |+|---|---|---|---|+| `activate_document` | Make a document current - the quiet lane (one in-process agent call, no cursor, no OS foreground). Board and schematic read verbs act on the CURRENT document. |  | `fusion_activate_document` |+| `agent_recover` | Un-halt the script engine: clear the PCB transaction, press OK on open Altium dialogs, send EditScript:Stop, reap the busy notice and delete stale request... |  |  |+| `agent_start` | Bootstrap the resident DelphiScript agent inside Altium (renders the script, runs it through the -R lane, waits for the heartbeat). Optional: every... |  |  |+| `agent_status` | Is the agent loop alive and responsive, in which workspace, and is any loop resident anywhere? Read-only; never starts the agent. |  |  |+| `agent_stop` | Stop the selected instance's resident agent loop (drops a stop file and waits up to 12 s for the heartbeat to go stale). |  |  |+| `animate_view` | Camera/zoom choreography on the current document - native Altium camera commands, animated by Altium itself. |  |  |+| `check_dialogs` | List every Altium dialog currently open, classified, each with a screenshot (the only way to READ one: Altium paints its dialog text). |  |  |+| `close` | Polite close (releases the licence seat). Frees a script engine parked on a breakpoint first (Altium refuses to close over one). discardChanges:true answers... | `kicad_close` | `fusion_stop` |+| `close_document` | Close ONE open document by path through its document handle. Never saves. |  | `fusion_close_document` |+| `connect_workspace` | Open Altium's workspace picker (the 'Not Connected' menu) and return a screenshot with a coordMap. The list is a custom control with no UIA children, so the... |  |  |+| `demo` | The tour. Default: one background beat per call with a screenshot. live:true = the narrated FOREGROUND show: ab caption + document + in-bridge camera... | `kicad_demo` | `fusion_demo` |+| `describe` | This list - the bridge's verbs and schemas for ab's Verbs tab. | `kicad_describe` | `fusion_describe` |+| `diagnostics` | Bridge-side facts for a bug report: environment variables, every Altium install found, Altium's windows, the agent status block and the cache directory. | `kicad_diagnostics` |  |+| `dialog_log` | What the standing dialog reaper (and the bridge's own recoveries) pressed, newest last: nuisance boxes, script-error boxes, discarded save prompts, silent... |  |  |+| `dismiss_dialogs` | Press one named button (default OK) on the detected Altium dialogs through UIA - no cursor, no focus steal. titles narrows it to dialogs whose title... | `kicad_dismiss_dialogs` | `fusion_dismiss_blocking_dialogs` |+| `ensure_licensed` | One call to get a usable Altium: launch if needed, then claim a seat (Use License via UIA). Optional escalations: sign out to force a seat release, or start... |  |  |+| `eval` | Run a DelphiScript snippet inside Altium (optional Var block, then Begin ... End;) on the CURRENT document, or on filePath opened first. |  |  |+| `extension_ping` | Does the AdomBridge .NET extension answer? Sends AdomBridge:Ping through the -R lane and waits up to 30 s for its result file. |  |  |+| `foreground` | Bring Altium in front of the user - the sanctioned exception to the never-foreground rule. |  |  |+| `install` | Install Altium Designer on this machine, STAGED, with the rules in the verb (from 0.2.29). stage: preflight / get_installer / launch / wait / license, or... |  | `fusion_install_fusion` |+| `install_extension` | Install, update or remove the AdomBridge .NET extension (the PowerShell installer shipped beside the bridge). Altium must be CLOSED. |  |  |+| `install_receipt` | The last bundle install's full receipt (proof frame included), for a client whose install call outlived ab's budget. |  |  |+| `instances` | Every running Altium (Designer + Designer Develop can run together). |  |  |+| `launch` | Start Altium (the newest numbered install, or the one install names) and wait for its main window, then hand the user's own window back. Answers... | `kicad_launch` | `fusion_start` |+| `license` | Drive the License Management page (UIA first - no cursor, no foreground). |  |  |+| `license_status` | Plain-language answer to 'what Altium licence am I on?': the state from the window title plus a capture of the License Management page. |  |  |+| `list_commands` | Search Altium's process-launcher catalog (about 1,100 Server:Command entries parsed from the install's System/*.rcs files) by text and/or server. |  |  |+| `open_document` | Open a document or project by path through the agent and show it without focusing it. An already-open document is activated, not reloaded. |  |  |+| `open_project` | Open a project (.PrjPcb / .PrjScr / .PrjMbd / .PrjHar / .LibPkg) by path. Same handler as altium_open_document. |  |  |+| `panel` | Activate a docked panel TAB (SCH Library, PCB Library, Components, Projects, Properties) in the BACKGROUND - the 'ganged-up view' needs the right panels in... |  |  |+| `progress` | The bridge's timing book: phases in flight right now (percent/ETA blocks ab paints on the taskbar), measured phase history and per-verb run statistics. | `kicad_progress` |  |+| `readiness` | The one-call gate: is Altium installed, running, licensed, and is the resident agent alive? Answers with Altium closed and never touches the script engine. | `kicad_readiness` | `fusion_readiness` |+| `run_command` | Run an Altium process (Server:Command) through the agent, on the CURRENT document or on filePath opened first. |  |  |+| `run_script` | One shot: run a procedure of YOUR OWN .PrjScr through Altium's -R command line. Only for when the agent is NOT resident. |  |  |+| `save` | Save the focused document, or open filePath first and save that (WorkspaceManager:SaveObject on the focused document). |  |  |+| `sch_zoom` | Find parts on a sheet: select them (everything else deselected), frame the view on them and return a proof frame. The schematic half of cross-probing (the... |  |  |+| `select_net` | Select one net as a whole on the current board (tracks, arcs, vias, pads, pours) and nothing else, optionally framing it; no net clears the selection. | `kicad_select_net` |  |+| `selection` | What the user has selected in the current board (components with position mm, rotation, side, footprint, size) or sheet (components with location mils and... |  |  |+| `selftest_script_error` | Regression test for the script-error safety net (from 0.2.23): makes the agent raise a REAL DelphiScript runtime error on purpose and checks that the bridge... |  |  |+| `set_layer_visibility` | Show or hide one layer of the current board (by layer id) and refresh the view and the layer tabs. |  |  |+| `setup_trial` | Onboarding state machine: from nothing to a working trial. Each call reports the stage it found (install / launch / signin / done) and the ONE next action;... |  |  |+| `show_2d_board` | Open a .PcbDoc, switch to 2D, zoom to fit, and return a proof frame. With no filePath it opens the bundled Bluetooth Sentinel board. | `kicad_show_2d_board` | `fusion_show_2d_board` |+| `show_3d_board` | Open a .PcbDoc, switch to 3D, zoom to fit, and return a proof frame. With no filePath it opens the bundled Bluetooth Sentinel board. | `kicad_show_3d_board` | `fusion_show_3d_board` |+| `show_3d_chip` | Open a PcbLib member in the footprint editor in 3D, zoom to fit, and capture a proof frame of the part's 3D body. | `kicad_show_3d_chip` |  |+| `show_footprint` | Open a PcbLib member in the footprint editor in 2D, zoom to fit, and capture a proof frame. | `kicad_show_footprint` | `fusion_show_footprint` |+| `show_library` | Open a .SchLib or .PcbLib (kind taken from the extension), select its first component, return a proof frame and the component names. | `kicad_show_library` | `fusion_show_library` |+| `show_project` | Open a project (.PrjPcb) through the agent and return a proof frame of Altium's main window. With no filePath it opens Altium's bundled example project... | `kicad_show_project` | `fusion_show_project` |+| `show_schematic` | Open a .SchDoc, zoom to fit, and return a proof frame. With no filePath it opens the bundled Bluetooth Sentinel top sheet. | `kicad_show_schematic` | `fusion_show_schematic` |+| `show_symbol` | Open a SchLib member in the Symbol editor and capture a background proof. | `kicad_show_symbol` | `fusion_show_symbol` |+| `signin` | Report-only alias of altium_signin_status: it does NOT sign anyone in. Signing in to Altium is a browser flow only the human can complete. |  | `fusion_signin` |+| `signin_status` | Account and Altium 365 workspace connection as Altium's own UI reports it: licence state from the title, and whether the workspace menu reads 'Not Connected'. |  |  |+| `state` | The whole picture in one call: licence state, windows, dialogs, a screenshot of every main/dialog window, and the agent's logical state (focused document,... | `kicad_state` | `fusion_state` |+| `status` | Is Altium running, licensed, and is the resident agent alive? | `kicad_status` |  |+| `uninstall` | Remove the bridge's own cache directory (agent workspaces, rendered agent scripts, the Altium 365 token file). Altium Designer and the user's designs are... | `kicad_uninstall` | `fusion_uninstall` |+| `window_info` | Altium's top-level windows plus any open dialogs, without screenshots. The cheap way to ask 'is a modal blocking the engine?'. | `kicad_window_info` |  |+| `zoom_to` | Frame the PCB view on a rectangle, a part (or several) or a whole net, with a margin. |  |  |++## platform++Specific to one tool's platform (Altium 365, OutJobs, integrated libraries). Not part of the standard; listed so nothing is lost.++| Verb | What it does | KiCad | Fusion 360 |+|---|---|---|---|+| `a365_configure` | Store the Altium 365 workspace domain and, optionally, a Bearer token created by hand in the workspace web UI (Admin - Developer). Local-only; tokens never... |  |  |+| `a365_files_upload` | Upload one local file to the workspace's Files Service and get a temporary opaque file id to pass to a library mutation. |  |  |+| `a365_library_overview` | One fixed read-only query: the workspace library's name, description, update time, component ids/names and footprint/symbol totals. |  |  |+| `a365_library_schema` | Read-only schema map: the GraphQL queries and mutations whose names mention lib, component, symbol, footprint, part or datasheet, with mutation argument types. |  |  |+| `a365_logout` | Revoke the refresh grant at Altium and remove every cached OAuth token. Workspace and client id stay configured. |  |  |+| `a365_oauth_begin` | Start the Altium 365 OAuth (PKCE + ActionWait) sign-in: the bridge starts listening FIRST, then returns the authorize URL for the user's browser. Works with... |  |  |+| `a365_oauth_cancel` | Cancel a waiting OAuth flow. |  |  |+| `a365_oauth_status` | State of the OAuth flow started by altium_a365_oauth_begin: idle / waiting / authorized / cancelled / error. |  |  |+| `a365_query` | Run one GraphQL query or mutation against the workspace's GraphQL gateway (https://<workspace>/svc/napi/gateway/graphql/) with the stored token. |  |  |+| `a365_refresh` | Exchange the stored refresh token for a new access token (and store a rotated refresh token if one comes back). |  |  |+| `a365_status` | Is the Altium 365 lane configured, is there a token, and is it LIVE (probed with a one-row GraphQL query)? |  |  |+| `compile_intlib` | Compile a .LibPkg into an integrated library (.IntLib). |  |  |+| `extract_intlib` | Extract the editable .SchLib/.PcbLib sources from a compiled .IntLib (Altium writes them next to the IntLib and opens them). |  |  |+| `import_kicad` | Import a KiCad file with Altium's own KiCad importer, driven through the AdomBridge .NET extension. |  |  |+| `outjob_info` | An OutJob's containers (output media), the outputs in each, and their key settings (units, decimals, plotted layer count), read from the .OutJob file:... |  |  |+| `run_outjob` | Run one output medium of an .OutJob (gerbers, drill, BOM, PDFs...) and list what it wrote under 'Project Outputs for <project>'. |  |  |+| `variants` | Assembly variants of a project (which parts are fitted on which build): list them, make or change one with not-fitted parts (or put parts back), or remove... |  |  |+| `workspace_place` | Put a part from the Altium 365 workspace on a sheet: search, pick a result, place it (Altium's own placement, so the part keeps its workspace link,... |  |  |+| `workspace_search` | Search the Altium 365 workspace's components (the shared cloud library Altium is signed in to) through Altium's own Components panel: name, description and... |  |  |
package.jsonadded+49
@@ -0,0 +1,49 @@+{+  "slug": "eda-bridge-standard",+  "version": "0.1.0",+  "type": "skill",+  "title": "EDA Bridge Standard: one way for an AI to drive every PCB tool",+  "description": "The standard every Adom EDA bridge follows so one AI can drive Altium, KiCad, Fusion 360 and any future PCB tool the same way: shared verb names in tiers, the board model JSON, reply and refusal conventions, and a coverage table of each bridge against the Altium reference.",+  "tags": [+    "eda",+    "pcb",+    "standard",+    "bridge",+    "altium",+    "kicad",+    "fusion360",+    "board-model",+    "json-schema",+    "ai"+  ],+  "files": [+    "README.md",+    "SKILL.md",+    "verbs/standard.json",+    "verbs/coverage.json",+    "schema/board.schema.json",+    "schema/companions.schema.json",+    "docs/board-model.md",+    "docs/conventions.md",+    "docs/coverage.md",+    "tools/generate.py",+    "tools/tiers.json",+    "tools/equivalents.json",+    "reference/altium-0.2.75.json",+    "reference/kicad-1.0.58.json",+    "reference/fusion360-1.11.44.json"+  ],+  "discovery_pitch": "The one standard for Adom's EDA bridges: shared verbs, one board model, the same checks on Altium, KiCad and Fusion 360.",+  "discovery_triggers": [+    "eda bridge standard",+    "same verbs for altium kicad and fusion",+    "standard verb names for eda bridges",+    "board model json for pcb tools",+    "routing_state schema",+    "which features does the kicad bridge lack",+    "compare altium kicad fusion bridges",+    "port an altium bridge feature to kicad",+    "build a new eda bridge",+    "bridge parity across eda tools"+  ]+}
reference/altium-0.2.75.jsonadded+6767
@@ -0,0 +1,6767 @@+{+ "bridge": "altium",+ "version": "0.2.75",+ "verbs": [+  {+   "example": {},+   "hint": "Cheapest health call - answers from ab's window list plus the agent heartbeat; never touches the script engine.",+   "input": {},+   "longRunning": false,+   "name": "altium_status",+   "output": {+    "agentAlive": "bool",+    "licenceState": "string",+    "licensed": "bool",+    "running": "bool"+   },+   "pitfalls": [+    "'running' can be true while 'licensed' is false - a trial lease can be stranded by a crash."+   ],+   "related": [+    "altium_readiness",+    "altium_agent_status",+    "altium_instances"+   ],+   "statusVerb": null,+   "summary": "Is Altium running, licensed, and is the resident agent alive?",+   "timeoutSeconds": 15+  },+  {+   "example": {},+   "hint": "With several running, other verbs REFUSE without instance:<pid|key|word> - guessing means acting inside another AI thread's Altium.",+   "input": {},+   "longRunning": false,+   "name": "altium_instances",+   "output": {+    "count": "int",+    "instances": "[{pid, key, edition, licensed, agentAlive, ...}]"+   },+   "pitfalls": [+    "Develop writes NO licence tail when it HOLDS the seat - blank licenceState there means licensed."+   ],+   "related": [+    "altium_status"+   ],+   "statusVerb": null,+   "summary": "Every running Altium (Designer + Designer Develop can run together).",+   "timeoutSeconds": 15+  },+  {+   "example": {+    "text": "Begin PCBServer.GetCurrentPCBBoard.ViewManager_FullUpdate; End;"+   },+   "hint": "Single quotes only (no double quotes). A Var block before Begin is allowed. Capture output by writing a file, not by returning a value. Pass filePath: after an export the OutJob is current and GetCurrentPCBBoard is Nil.",+   "input": {+    "filePath": "optional: the document to make current first (a .PcbDoc for PCBServer.GetCurrentPCBBoard)",+    "text": "string - [Var X : Integer;] Begin ... End;",+    "timeout": "int seconds"+   },+   "longRunning": false,+   "name": "altium_eval",+   "output": {+    "errorCode": "script_error",+    "errorShot": "PNG of Altium's error box",+    "success": "bool (false + errorShot when Altium raised an error box)"+   },+   "pitfalls": [+    "An undeclared identifier HALTS the engine with a modal that Try/Except cannot catch.",+    "ShowMessage blocks the loop behind a modal - never use it."+   ],+   "related": [+    "altium_run_command"+   ],+   "statusVerb": null,+   "summary": "Run a DelphiScript snippet inside Altium (optional Var block, then Begin ... End;) on the CURRENT document, or on filePath opened first.",+   "timeoutSeconds": 30+  },+  {+   "example": {+    "command": "PCB:Zoom",+    "params": {+     "Action": "All"+    }+   },+   "hint": "Params are pipe-joined key=value pairs. altium_list_commands searches the catalog.",+   "input": {+    "command": "string - e.g. PCB:Zoom",+    "filePath": "optional: the document to make current first (PCB: processes need a board current)",+    "params": "string or object"+   },+   "longRunning": false,+   "name": "altium_run_command",+   "output": {+    "success": "bool"+   },+   "pitfalls": [+    "Some processes open a modal dialog and block the loop until it is answered."+   ],+   "related": [+    "altium_eval",+    "altium_list_commands"+   ],+   "statusVerb": null,+   "summary": "Run an Altium process (Server:Command) through the agent, on the CURRENT document or on filePath opened first.",+   "timeoutSeconds": 60+  },+  {+   "example": {},+   "hint": "Check before and after altium_install_library / altium_uninstall_library without a screenshot.",+   "input": {},+   "longRunning": false,+   "name": "altium_list_installed_libraries",+   "output": {+    "count": "int",+    "libraries": "[path]"+   },+   "pitfalls": [+    "Needs the agent (it reads IntegratedLibraryManager inside Altium)."+   ],+   "related": [+    "altium_uninstall_library",+    "altium_install_library",+    "altium_install_library_bundle"+   ],+   "statusVerb": null,+   "summary": "Every library in Altium's Installed Libraries list (read-only).",+   "timeoutSeconds": 30+  },+  {+   "example": {+    "libraryName": "Adom_ToF"+   },+   "hint": "Any open document for a removed library is closed first. Close Altium with a plain altium_close afterwards: a close that discards changes can drop the unregistration.",+   "input": {+    "dryRun": "bool",+    "libraryName": "file stem: matches X.SchLib AND X.PcbLib",+    "libraryPath": "exact registered path"+   },+   "longRunning": false,+   "name": "altium_uninstall_library",+   "output": {+    "closedDocuments": "[path]",+    "notInstalled": "bool",+    "remaining": "int",+    "removed": "[path]"+   },+   "pitfalls": [+    "Nothing matching answers notInstalled:true (success), so a cleanup can run twice.",+    "discardChanges:true on altium_close can bring the removed library back on the next launch."+   ],+   "related": [+    "altium_list_installed_libraries",+    "altium_install_library",+    "altium_close"+   ],+   "statusVerb": null,+   "summary": "Remove libraries from Altium's Installed Libraries: the inverse of install_library / install_library_bundle. Library files are never touched.",+   "timeoutSeconds": 60+  },+  {+   "example": {+    "name": "SCH Library"+   },+   "hint": "One UIA search reads the tab's existence AND selection, then at most one SelectionItemPattern select and one verify, all inside a 24 s budget: every exit is a named answer (panel_not_found, panel_search_timeout, ui_select_gated, panel_not_selected), never ab's generic timeout.",+   "input": {+    "ensureId": "string optional - panel ID for Client:CustomizeResources (e.g. TSchLibPanel) when the panel may not exist yet",+    "name": "string - the tab caption exactly as painted, e.g. 'SCH Library'"+   },+   "longRunning": false,+   "name": "altium_panel",+   "output": {+    "elapsedSec": "float",+    "lane": "already | ui_select | ui_select_raised",+    "selected": "bool",+    "verified": "bool (false when the tab exposes no selection state)"+   },+   "pitfalls": [+    "ab gates desktop_ui_select behind the shell approval grant; without it the verb reports ui_select_gated instead of pretending.",+    "A plain click does not switch panel tabs - that is why this verb exists.",+    "On Altium Designer Develop the UI Automation search does not finish in time (panel_search_timeout, measured 2026-10-15); opening a library document already brings its library panel to the front.",+    "lane ui_select_raised means ab fell back to a synthetic click that foregrounded Altium; the dispatch wrapper hands the user's window back."+   ],+   "related": [+    "altium_show_symbol",+    "altium_show_footprint",+    "altium_run_command"+   ],+   "statusVerb": null,+   "summary": "Activate a docked panel TAB (SCH Library, PCB Library, Components, Projects, Properties) in the BACKGROUND - the 'ganged-up view' needs the right panels in front, and no Altium server process switches a tab.",+   "timeoutSeconds": 30+  },+  {+   "example": {},+   "hint": "The primitive every board view derives from; board_summary / rules_by_kind / layers_in_use are shaped from the same read.",+   "input": {+    "limit": "int",+    "sections": "string csv - optional"+   },+   "longRunning": false,+   "name": "altium_read_board",+   "output": {+    "data": "object - the agent's payload",+    "roundtripMs": "int",+    "success": "bool"+   },+   "pitfalls": [+    "Needs a .PcbDoc current."+   ],+   "related": [+    "altium_board_summary",+    "altium_review_board"+   ],+   "statusVerb": null,+   "summary": "ONE round trip: components, nets, layers, copper, rules, occupancy, outline.",+   "timeoutSeconds": 120+  },+  {+   "example": {+    "includeSchematic": true+   },+   "hint": "PARTIAL results by design: skipped says which checks had no data; a clean result means the checks that RAN found nothing.",+   "input": {+    "board": "path - optional",+    "checks": "[string] - optional filter",+    "includeSchematic": "bool",+    "sheet": "path - optional"+   },+   "longRunning": false,+   "name": "altium_review_board",+   "output": {+    "counts": "{}",+    "findings": "[{check, severity, message, ...}]",+    "notImplemented": "{}",+    "skipped": "{}"+   },+   "pitfalls": [+    "Altium holds ONE current document - the review activates board and sheet in turn."+   ],+   "related": [+    "altium_read_board",+    "altium_cross_check"+   ],+   "statusVerb": null,+   "summary": "Design-review checks (14) over one board read; schematic checks with includeSchematic.",+   "timeoutSeconds": 120+  },+  {+   "example": {},+   "hint": "Check the step counts: models:0 fails the call - a symbol with no footprint model places with nothing attached. Importing again REPLACES the symbol and the footprint's 3D body (add_body step: bodiesNow 1); boards that place the part need PCB:UpdateFootprints.",+   "input": {+    "comment": "string",+    "description": "string",+    "footprintSpec": "path",+    "params": "{}",+    "pcbLib": "path",+    "schLib": "path",+    "step": "path",+    "symbolSpec": "path"+   },+   "longRunning": false,+   "name": "altium_import_part",+   "output": {+    "footprint": "string",+    "steps": "[] - the add_body step carries replacedBodies, bodiesNow, sameNameFootprints",+    "symbol": "string"+   },+   "pitfalls": [+    "add_models APPENDS on every call (not idempotent) - repair is remove_symbol + re-import.",+    "The add_body step's sameNameFootprints > 1 means the library holds stale footprints of that name: the body went to the FIRST only.",+    "The first part into a NEW library can silently skip models+params if the bridge restarted mid-flight - verify with read_symbol."+   ],+   "related": [+    "altium_read_symbol",+    "altium_show_symbol"+   ],+   "statusVerb": null,+   "summary": "One call imports a whole part: footprint spec + STEP + symbol modes + models + params + save.",+   "timeoutSeconds": 300+  },+  {+   "example": {+    "footprintName": "VL53L5CX;VL53L7CX;VL53L8CX",+    "hMm": 14,+    "libraryPath": "C:/lib/Adom_ToF.PcbLib",+    "pcbPath": "C:/proj/tof.PcbDoc",+    "wMm": 39+   },+   "hint": "Three parts on one board: footprintName 'VL53L5CX;VL53L7CX;VL53L8CX' + pcbPath, then altium_show_3d_board {filePath: pcbPath}. Run it again with the same pcbPath to ADD parts: they land right of the current ones, designators continue (U4...).",+   "input": {+    "assigns": "des=NET;...",+    "footprintName": "string | 'A;B;C'",+    "hMm": "number (new board only)",+    "libraryPath": "path (.PcbLib)",+    "pcbPath": "path (.PcbDoc) - existing = append, new = create+save",+    "stubs": "des=x,y;...",+    "wMm": "number (new board only)"+   },+   "longRunning": false,+   "name": "altium_test_board",+   "output": {+    "appended": "bool",+    "bodiesOnComp": "int",+    "count": "int",+    "existingParts": "int",+    "padsOnComp": "int",+    "pcb": "path",+    "placed": "string",+    "saved": "bool (checked on disk)"+   },+   "pitfalls": [+    "assigns/stubs apply to the LAST part placed.",+    "pcb is empty when pcbPath is omitted: the board exists only unsaved as PCB<n>.PcbDoc.",+    "Appending ignores wMm/hMm and does not grow the outline: parts can land outside it - resize with altium_board_shape if needed.",+    "Each part's name is its own Comment (moves with the part) and its Pattern is the footprint name.",+    "errorCode not_saved: the board was built but the file did not land; the folder is created automatically."+   ],+   "related": [+    "altium_place_on_sch",+    "altium_show_3d_board",+    "altium_add_body"+   ],+   "statusVerb": null,+   "summary": "Place one footprint, or a ';' list, from the open PcbLib on ONE board: a NEW board (wMm x hMm outline) when pcbPath is new or omitted, APPENDED to the existing board when pcbPath already exists (issue 11).",+   "timeoutSeconds": 240+  },+  {+   "example": {+    "libraryPath": "C:/lib/Adom_ToF.PcbLib"+   },+   "hint": "altium_save the PcbLib afterwards; footprint_primitives on a placed part should then report layerId 74 for the vias.",+   "input": {+    "footprintName": "string (optional filter)",+    "libraryPath": "path (.PcbLib)"+   },+   "longRunning": false,+   "name": "altium_plate_vias",+   "output": {+    "fixed": "Fp:Pad;...",+    "holes": "int",+    "plated": "int"+   },+   "pitfalls": [+    "Read-modify in place: run once per library install."+   ],+   "related": [+    "altium_normalize_bodies",+    "altium_footprint_primitives",+    "altium_save"+   ],+   "statusVerb": null,+   "summary": "Make every drilled pad in a PcbLib a plated MULTI-LAYER pad (all footprints, or footprintName). adom-lbr writes heatsink vias as pads with a hole; Altium loaded them on the Top Layer, so a thermal pad's vias were not through-holes until this ran.",+   "timeoutSeconds": 90+  },+  {+   "example": {+    "humanIsPresent": true,+    "stage": "launch"+   },+   "hint": "Call it with no args and follow _next. The human's part ENDS at the installer's Finish button; licensing is yours.",+   "input": {+    "humanIsPresent": "bool, REQUIRED true for stage launch: the UAC prompt auto-denies in ~2 minutes",+    "installerPath": "optional path; default = newest AltiumDesignerSetup*.exe in Downloads",+    "stage": "auto (default) | preflight | get_installer | launch | wait | license"+   },+   "longRunning": false,+   "name": "altium_install",+   "output": {+    "_next": "[string]",+    "licensed": "bool",+    "needsHuman": "[string] - only ever a password, the UAC prompt, or clicks in an elevated window",+    "stage": "string",+    "toast": "{displayed, _hint}"+   },+   "pitfalls": [+    "needsHuman may only name a password/2FA, the UAC prompt, or clicks inside an ELEVATED window. Release, Assign, Use License, Refresh and save prompts are ordinary clicks: do them.",+    "LOOK before you ask: 'Sign Out' on the licence page means the user is ALREADY signed in (the installer's browser sign-in carries over).",+    "'There are no seats available' means another running Altium holds the account's single On-Demand seat: altium_close it there, then stage license again.",+    "Never use the installer's unattended switches: they take the account password on the command line.",+    "A toast only reaches the machine it is shown on. Check toast.displayed and ALSO tell the user in chat."+   ],+   "related": [+    "altium_ensure_licensed",+    "altium_license",+    "altium_launch",+    "altium_readiness"+   ],+   "statusVerb": null,+   "summary": "Install Altium Designer on this machine, STAGED, with the rules in the verb (from 0.2.29). stage: preflight | get_installer | launch | wait | license, or omit it and 'auto' picks the stage from the machine's state. The verb looks before it asks, verifies the installer's Authenticode signature (Valid, CN=Altium LLC) before anything runs, refuses to launch into an empty room, toasts the human itself and says truthfully whether the toast displayed, and does the licensing clicks (Refresh / Use License) ITSELF.",+   "timeoutSeconds": 120+  },+  {+   "example": {},+   "hint": "netWorks:true is the pass. The agent is stopped afterwards by design; the next verb restarts it.",+   "input": {},+   "longRunning": false,+   "name": "altium_selftest_script_error",+   "output": {+    "autoRecovered": "bool",+    "dialogsLeft": "int",+    "errorShot": "base64 png (halted outcome only)",+    "netWorks": "bool",+    "outcome": "halted_and_recovered | contained_by_agent | unhandled"+   },+   "pitfalls": [+    "It really does halt the engine for a moment: do not run it while a user is mid-edit in Altium.",+    "By design this is the SLOWEST verb in the catalog: it halts the engine, waits for the halt to be seen, recovers it and verifies by restarting the agent. Altium Designer Develop parks SILENTLY (no Error box), so the halt is only seen when the wait expires \u2014 measured 18 s to over 60 s on the same machine. Its budget is 180 s for that reason; a timeout here means the safety net is broken, not that it was slow."+   ],+   "related": [+    "altium_agent_recover",+    "altium_dialog_log",+    "altium_agent_status"+   ],+   "statusVerb": null,+   "summary": "Regression test for the script-error safety net (from 0.2.23): makes the agent raise a REAL DelphiScript runtime error on purpose and checks that the bridge photographs Altium's Error box, stops the halted script itself and leaves no dialog behind. Flashes one error box for under a second. Run it after any change to agent.rs / dialogs.rs and once on every new Altium build.",+   "timeoutSeconds": 180+  },+  {+   "example": {},+   "hint": "polylines should equal what add_symbol_art reported and polyBboxMil should sit inside bodyRectMil.",+   "input": {+    "libraryPath": "path (.SchLib)",+    "models": "bool, default false (the model walk halts the engine on some builds)",+    "symbolName": "string"+   },+   "longRunning": false,+   "name": "altium_symbol_prims",+   "output": {+    "bodyRectMil": "[l, b, r, t] - the union of the current display mode's rectangles (mode 0, as a sheet draws it)",+    "lines": "int",+    "models": "[\"PCBLIB:NAME\"]",+    "pins": "int",+    "polyBboxMil": "[..]",+    "polylines": "int",+    "rectangles": "int"+   },+   "pitfalls": [],+   "related": [+    "altium_add_symbol_art",+    "altium_set_models",+    "altium_read_symbol"+   ],+   "statusVerb": null,+   "summary": "Read a SchLib component's drawing: primitive counts by kind, body rectangle, polyline bounding box, display mode and the footprint models. Read-only, use it to verify add_symbol_art / set_models.",+   "timeoutSeconds": 60+  },+  {+   "example": {},+   "hint": "altium_save the SchLib afterwards.",+   "input": {+    "footprint": "string (PcbLib footprint name)",+    "libraryPath": "path (.SchLib)",+    "symbolName": "string"+   },+   "longRunning": false,+   "name": "altium_set_models",+   "output": {+    "footprint": "string",+    "removed": "int"+   },+   "pitfalls": [+    "Removes every existing model, including 3D-only ones."+   ],+   "related": [+    "altium_symbol_prims",+    "altium_set_params",+    "altium_add_models"+   ],+   "statusVerb": null,+   "summary": "Replace every footprint model on a SchLib component with ONE PCBLIB model (set_params footprint and add_models APPEND, which is how a symbol ends up with two).",+   "timeoutSeconds": 60+  },+  {+   "example": {+    "kicadSym": "C:/lib/VL53L5CX.kicad_sym",+    "libraryPath": "C:/lib/Adom_ToF.SchLib",+    "symbolName": "VL53L5CX"+   },+   "hint": "Run once per part right after assemble-altium / build_symbol_modes, then altium_save the SchLib and altium_show_symbol to check.",+   "input": {+    "clear": "bool, default true: remove the component's existing polylines/lines first",+    "kicadSym": "Windows path to the .kicad_sym that carries the baked outline (stage it with send_files)",+    "libraryPath": "path (.SchLib)",+    "mode": "lines (default) | polyline",+    "symbolName": "string (LibReference)"+   },+   "longRunning": false,+   "name": "altium_add_symbol_art",+   "output": {+    "bodyRectMil": "[x0,y0,x1,y1]",+    "points": "int",+    "polylines": "int"+   },+   "pitfalls": [+    "clear:true (default) also removes hand-drawn lines on the symbol; only the pins and the body rectangle survive.",+    "The KiCad symbol must already carry the outline (adom-symbol create bakes it; --outline-name none for side-etched chips)."+   ],+   "related": [+    "altium_show_symbol",+    "altium_symbol_prims",+    "altium_save"+   ],+   "statusVerb": null,+   "summary": "Draw the chip-outline artwork (the chip-thumbnailer polylines adom-symbol baked into the KiCad symbol) into a SchLib component, fitted to its body rectangle, so the Altium symbol shows the same chip as KiCad and Fusion.",+   "timeoutSeconds": 180+  },+  {+   "example": {+    "libraryPath": "C:/lib/Adom_ToF.SchLib",+    "schPath": "C:/proj/tof.SchDoc",+    "symbolName": "VL53L5CX;VL53L7CX;VL53L8CX"+   },+   "hint": "Three parts on one sheet: symbolName 'VL53L5CX;VL53L7CX;VL53L8CX' + schPath, then altium_show_schematic {filePath: schPath}.",+   "input": {+    "description": "string (what the sheet shows, 2-3 sentences)",+    "drawnBy": "string",+    "libraryPath": "path (.SchLib)",+    "number": "string (DocumentNumber)",+    "revision": "string",+    "schPath": "path (.SchDoc, optional save)",+    "symbolName": "string | 'A;B;C'",+    "title": "string (Title)"+   },+   "longRunning": false,+   "name": "altium_place_on_sch",+   "output": {+    "count": "int",+    "designator": "string",+    "placed": "string",+    "primitives": "int",+    "sch": "path"+   },+   "pitfalls": [+    "Every call creates a NEW SchDoc; there is no append-to-sheet yet.",+    "The SchLib must be the current document (the verb opens it)."+   ],+   "related": [+    "altium_test_board",+    "altium_show_schematic",+    "altium_read_schematic"+   ],+   "statusVerb": null,+   "summary": "Place one symbol, or a ';' list of symbols, from the open SchLib on ONE new sheet (sheet sized FROM the measured parts: 500 mil gaps, 600 mil margins, A4 minimum; parts inside the frame as U1..Un with the part number as Comment); fills the title block from title/number/revision/drawnBy and places description as a text frame; saves to schPath when given. When schPath names an EXISTING .SchDoc the symbols are APPENDED to it (right of the current ones, designators continuing; sheet size and title block untouched, issue 11).",+   "timeoutSeconds": 90+  },+  {+   "example": {},+   "hint": "Never foregrounds: a minimized Altium is restored INTO THE BACKGROUND and the user's window handed straight back.",+   "input": {+    "libraryPath": "path",+    "symbolName": "string"+   },+   "longRunning": false,+   "name": "altium_show_symbol",+   "output": {+    "shot": "{image: base64 png, width, height}",+    "success": "bool",+    "windowEtiquette": "background"+   },+   "pitfalls": [+    "A capture under 400px wide is refused as proof_unavailable - a title-bar sliver is not evidence."+   ],+   "related": [+    "altium_show_footprint",+    "altium_show_3d_chip",+    "altium_show_library"+   ],+   "statusVerb": null,+   "summary": "Open a SchLib member in the Symbol editor and capture a background proof.",+   "timeoutSeconds": 60+  },+  {+   "example": {+    "preset": "showcase3d"+   },+   "hint": "Runs IN-BRIDGE (~40ms per step to the agent) so timing is tight enough to narrate over. Steps: zoom_all/in/out, view_2d/3d, isometric, top/bottom/front/back/left/right, rotate90, ortho, orbit_left/right/up/down, rolls, pans, flip.",+   "input": {+    "lane": "pcb (default) | sch",+    "pauseMs": "int default per-step pause",+    "preset": "punch_in|showcase3d|orbit|flip|finale",+    "steps": "[string | {step, pauseMs}]"+   },+   "longRunning": false,+   "name": "altium_animate_view",+   "output": {+    "elapsedSec": "float",+    "stepsRun": "int"+   },+   "pitfalls": [+    "Driving camera steps one-by-one from a container adds ~1.4s of jitter per step - that is what this verb exists to avoid.",+    "sch lane supports zoom steps only; everything else needs a PCB document current."+   ],+   "related": [+    "altium_demo",+    "altium_foreground",+    "altium_run_command"+   ],+   "statusVerb": null,+   "summary": "Camera/zoom choreography on the current document - native Altium camera commands, animated by Altium itself.",+   "timeoutSeconds": 120+  },+  {+   "example": {+    "reason": "John asked to watch the import live"+   },+   "hint": "For a human who asked to WATCH (a live demo, a walkthrough). Everything else stays background.",+   "input": {+    "reason": "string >=10 chars, written for the HUMAN (required)"+   },+   "longRunning": false,+   "name": "altium_foreground",+   "output": {+    "windowEtiquette": "foreground"+   },+   "pitfalls": [+    "Refused without a real reason - same rule as ab's desktop_caption."+   ],+   "related": [+    "altium_demo",+    "altium_animate_view"+   ],+   "statusVerb": null,+   "summary": "Bring Altium in front of the user - the sanctioned exception to the never-foreground rule.",+   "timeoutSeconds": 15+  },+  {+   "example": {+    "live": true,+    "stage": 0+   },+   "hint": "live SYNC MODEL: pre-cache narration with `adom-tts load` (one marker per stop), then per stop fire `adom-tts cue <id> <n> --confirm` in the background and IMMEDIATELY make nextCall - the prefetched cue starts instantly and the ~5-17s of on-screen motion runs under the voice; advance when BOTH finish. If Altium is cold, the first call answers status:launching with a narrate line - speak it, poll again in ~20s.",+   "input": {+    "live": "bool",+    "stage": "int 0-5"+   },+   "longRunning": false,+   "name": "altium_demo",+   "output": {+    "done": "bool",+    "narrate": "string - speak this",+    "nextCall": "{verb, args}",+    "nextNarrate": "string"+   },+   "pitfalls": [+    "The caller owns the VOICE (adom-tts) - the bridge never synthesizes speech.",+    "After done:true, restore the user's own document (altium_activate_document)."+   ],+   "related": [+    "altium_animate_view",+    "altium_foreground",+    "altium_show_symbol"+   ],+   "statusVerb": null,+   "summary": "The tour. Default: one background beat per call with a screenshot. live:true = the narrated FOREGROUND show: ab caption + document + in-bridge camera choreography per stop, returning the narration text for YOUR TTS.",+   "timeoutSeconds": 180+  },+  {+   "example": {+    "action": "use_license"+   },+   "hint": "One On-Demand seat serves EVERY Altium app on this machine. use_license claims a free seat.",+   "input": {+    "action": "show|refresh|use_license|start_trial|sign_out"+   },+   "longRunning": false,+   "name": "altium_license",+   "output": {+    "shot": "{image: base64 png, width, height}",+    "success": "bool",+    "windowEtiquette": "background"+   },+   "pitfalls": [+    "NEVER kill an Altium holding the seat: the lease strands, the page shows Seats 0/1 while every claim fails. altium_close releases it."+   ],+   "related": [+    "altium_ensure_licensed",+    "altium_license_status",+    "altium_close"+   ],+   "statusVerb": null,+   "summary": "Drive the License Management page (UIA first - no cursor, no foreground).",+   "timeoutSeconds": 60+  },+  {+   "example": {},+   "hint": "A save prompt on close NAMES the documents - read it; on a shared machine one may belong to another AI thread. Never defaulted.",+   "input": {+    "acceptSeatLoss": "bool",+    "discardChanges": "bool, default false: press Save None + confirm on 'Confirm Save Locally'",+    "force": "bool",+    "instance": "pid, when several Altiums run",+    "waitSec": "float, default 20"+   },+   "longRunning": false,+   "name": "altium_close",+   "output": {+    "closedInSec": "float",+    "engineFreed": "object|null",+    "errorCode": "unsaved_changes | close_blocked_by_dialog | altium_modal_orphan | modal_orphan_survived_quit | close_timeout",+    "stuckModalOrphan": "{pid,tid,hwnd} when Altium was wedged in a modal loop and the bridge ended it gracefully"+   },+   "pitfalls": [+    "force:true without acceptSeatLoss is refused: a kill strands the On-Demand seat.",+    "errorCode altium_modal_orphan = Altium is stuck inside a modal loop whose dialog is GONE (its main window is disabled, nothing on screen to answer). No menu, keystroke or -R reaches it and it still holds the seat. With discardChanges:true the bridge ends it by posting WM_QUIT - Altium's own shutdown, which RELEASES the seat. Never reach for force/taskkill first."+   ],+   "related": [+    "altium_license",+    "altium_launch"+   ],+   "statusVerb": null,+   "summary": "Polite close (releases the licence seat). Frees a script engine parked on a breakpoint first (Altium refuses to close over one). discardChanges:true answers Altium's save prompts with Save None for callers whose modified documents are ALL their own; force needs acceptSeatLoss.",+   "timeoutSeconds": 120+  },+  {+   "example": {+    "capture": true,+    "installId": "Lib-1757350000000",+    "libraryName": "Lib",+    "zipPath": "C:\\Users\\me\\adom-wiki-libs\\Lib-altium.zip"+   },+   "hint": "Launches a closed Altium first (up to 240s), then registers and opens the library. Past ab's budget the verb keeps running: poll altium_status until lastInstall.installId matches, then altium_install_receipt for the full receipt.",+   "input": {+    "capture": "bool",+    "captureMaxWidth": "int",+    "installId": "string - echoed in the receipt so a client can match it",+    "libraryName": "string",+    "zipPath": "string - staged zip"+   },+   "longRunning": true,+   "name": "altium_install_library_bundle",+   "output": {+    "atMs": "int",+    "components": "[names]",+    "installId": "string",+    "phasesMs": "{launch, licence, unpack, register, open, list, panel, capture, agentStop}",+    "shot": "{image, ...}",+    "shown": "bool",+    "symbolCount": "int",+    "thumbnailDataUrl": "string",+    "totalMs": "int"+   },+   "pitfalls": [+    "The receipt lives in bridge memory; a bridge restart clears it."+   ],+   "related": [+    "altium_install_receipt",+    "altium_status",+    "altium_install_library"+   ],+   "statusVerb": "altium_status",+   "summary": "One-click install of a wiki library bundle (zip of .SchLib/.PcbLib): unpack, register, open the library, prove it with a frame.",+   "timeoutSeconds": 330+  },+  {+   "example": {},+   "hint": "Read after altium_status shows lastInstall with your installId.",+   "input": {},+   "longRunning": false,+   "name": "altium_install_receipt",+   "output": {+    "receipt": "object|null - the install_library_bundle reply"+   },+   "pitfalls": [],+   "related": [+    "altium_install_library_bundle",+    "altium_status"+   ],+   "statusVerb": null,+   "summary": "The last bundle install's full receipt (proof frame included), for a client whose install call outlived ab's budget.",+   "timeoutSeconds": 10+  },+  {+   "example": {},+   "hint": "Static; safe to call any time.",+   "input": {},+   "longRunning": false,+   "name": "altium_describe",+   "output": {+    "verbs": "[{name, summary, input, output, ...}]"+   },+   "pitfalls": [],+   "related": [+    "altium_status"+   ],+   "statusVerb": null,+   "summary": "This list - the bridge's verbs and schemas for ab's Verbs tab.",+   "timeoutSeconds": 5+  },+  {+   "example": {},+   "hint": "Follow _hint: not installed -> altium_install; not running -> altium_launch; licensed:false -> altium_ensure_licensed; agent down -> just call the verb you need (it bootstraps the agent) or altium_agent_start. ready:true -> altium_state.",+   "input": {},+   "longRunning": false,+   "name": "altium_readiness",+   "output": {+    "adVersion": "string | null",+    "agentAlive": "bool - the selected instance",+    "agentLiveWorkspaces": "[{workspace, heartbeatAgeSec}] - only when a loop lives outside the selection",+    "agentResidentAnywhere": "bool - any workspace",+    "install": "{base_dir, exe, system_dir, major} | null",+    "installed": "bool",+    "licenceState": "string",+    "licensed": "bool | null",+    "ready": "bool - all four true",+    "running": "bool"+   },+   "pitfalls": [+    "agentAlive is the SELECTED instance's view. agentResidentAnywhere:true beside agentAlive:false means a loop lives in another workspace: close through altium_close, never the window X."+   ],+   "related": [+    "altium_status",+    "altium_launch",+    "altium_ensure_licensed",+    "altium_agent_start"+   ],+   "statusVerb": null,+   "summary": "The one-call gate: is Altium installed, running, licensed, and is the resident agent alive? Answers with Altium closed and never touches the script engine.",+   "timeoutSeconds": 15+  },+  {+   "example": {},+   "hint": "installs:[] means Altium is not installed (altium_install). Otherwise altium_readiness for the verdict and altium_instances when several Altiums run.",+   "input": {},+   "longRunning": false,+   "name": "altium_diagnostics",+   "output": {+    "agent": "the altium_agent_status reply",+    "cacheDir": "path",+    "env": "{LOCALAPPDATA, APPDATA, USERPROFILE, ProgramFiles, ADOM_BIND_HOST, ADOM_DIRECT_API_URL}",+    "installs": "[{base_dir, exe, system_dir, major}] newest major first",+    "windows": "[{hwnd, title, kind, className, pid, rect}]"+   },+   "pitfalls": [+    "Instance-exempt: it runs with no instance selected, so agent.alive reads the legacy workspace; agent.liveWorkspaces is the union that counts."+   ],+   "related": [+    "altium_readiness",+    "altium_agent_status",+    "altium_instances"+   ],+   "statusVerb": null,+   "summary": "Bridge-side facts for a bug report: environment variables, every Altium install found, Altium's windows, the agent status block and the cache directory.",+   "timeoutSeconds": 15+  },+  {+   "example": {},+   "hint": "hasModalDialogs:true means the agent loop and every -R request are frozen behind it: altium_check_dialogs for the pictures, then altium_dismiss_dialogs {button, titles}. Otherwise the main window's title names the current document.",+   "input": {},+   "longRunning": false,+   "name": "altium_window_info",+   "output": {+    "dialogs": "[{hwnd, title, body, buttons, category, safeAction, ownedByMainInstance}]",+    "hasModalDialogs": "bool - a dialog owned by the main instance",+    "windows": "[{hwnd, title, kind, className, pid, rect}]"+   },+   "pitfalls": [+    "Altium PAINTS its dialog text, so body is often empty here; altium_check_dialogs attaches a PNG per dialog."+   ],+   "related": [+    "altium_check_dialogs",+    "altium_dismiss_dialogs",+    "altium_state"+   ],+   "statusVerb": null,+   "summary": "Altium's top-level windows plus any open dialogs, without screenshots. The cheap way to ask 'is a modal blocking the engine?'.",+   "timeoutSeconds": 15+  },+  {+   "example": {+    "screenshots": false+   },+   "hint": "Look before acting: logical.data.focusedDocument is what the board/schematic read verbs would read; altium_activate_document {path} changes it. Poll with screenshots:false.",+   "input": {+    "screenshots": "bool, default true"+   },+   "longRunning": false,+   "name": "altium_state",+   "output": {+    "agentAlive": "bool - union over every workspace",+    "agentWorkspace": "path | null - where logical came from",+    "dialogs": "[..]",+    "drivingBadge": "{active}",+    "licence": "{running, licensed, state, adVersion, edition, document, hwnd, pid}",+    "liveWorkspaces": "[{workspace, heartbeatAgeSec}]",+    "logical": "agent reply; data = {focusedDocument, focusedProject, documents:[{path, kind, project, open}]} | null when no agent is alive",+    "shots": "[{title, hwnd, kind, image: base64 png, width, height}]",+    "windows": "[..]"+   },+   "pitfalls": [+    "logical is null when no agent is resident: state never bootstraps one. Call any agent-backed verb or altium_agent_start first.",+    "shots[] are full-size base64 PNGs of every main and dialog window; pass screenshots:false when you only need the data."+   ],+   "related": [+    "altium_status",+    "altium_window_info",+    "altium_activate_document",+    "altium_agent_start"+   ],+   "statusVerb": null,+   "summary": "The whole picture in one call: licence state, windows, dialogs, a screenshot of every main/dialog window, and the agent's logical state (focused document, focused project, open documents).",+   "timeoutSeconds": 60+  },+  {+   "example": {},+   "hint": "READ dialogs[].shot, decide, then altium_dismiss_dialogs {button, titles:[..]}. A script 'Error' box: altium_agent_recover. A save prompt on close: see altium_close discardChanges.",+   "input": {+    "screenshots": "bool, default true"+   },+   "longRunning": false,+   "name": "altium_check_dialogs",+   "output": {+    "dialogs": "[{hwnd, pid, title, className, body, buttons, category, safeAction, meaning, ownedByMainInstance, rect, shot: base64 png}]",+    "dialogsDetected": "int"+   },+   "pitfalls": [+    "category and safeAction are suggestions. A save_prompt is never auto-answered: it names documents that may belong to the user or another AI thread.",+    "Known nuisance boxes are pressed by the standing reaper before you ever see them; altium_dialog_log lists those."+   ],+   "related": [+    "altium_dismiss_dialogs",+    "altium_dialog_log",+    "altium_agent_recover",+    "altium_window_info"+   ],+   "statusVerb": null,+   "summary": "List every Altium dialog currently open, classified, each with a screenshot (the only way to READ one: Altium paints its dialog text).",+   "timeoutSeconds": 30+  },+  {+   "example": {+    "button": "OK",+    "titles": [+     "Error"+    ]+   },+   "hint": "ALWAYS pass titles. Then altium_check_dialogs to confirm nothing is left, and repeat the verb that was blocked.",+   "input": {+    "button": "string, default 'OK' - the button's accessible name",+    "titles": "[string] - optional title substrings; only matching dialogs are pressed"+   },+   "longRunning": false,+   "name": "altium_dismiss_dialogs",+   "output": {+    "detected": "int",+    "pressed": "[{title, hwnd, button, ok}]"+   },+   "pitfalls": [+    "Without titles the button is pressed on EVERY detected Altium dialog, save prompts included.",+    "hwnd is NOT read by this verb, although other verbs' hints say {hwnd, button}: target a dialog with titles.",+    "success is true even when nothing was pressed or the click failed: check pressed[].ok."+   ],+   "related": [+    "altium_check_dialogs",+    "altium_close",+    "altium_agent_recover"+   ],+   "statusVerb": null,+   "summary": "Press one named button (default OK) on the detected Altium dialogs through UIA - no cursor, no focus steal. titles narrows it to dialogs whose title contains one of the strings.",+   "timeoutSeconds": 30+  },+  {+   "example": {},+   "hint": "Cold start is about 60 s. Next: altium_readiness; licensed:false -> altium_ensure_licensed. Most verbs need this first (errorCode altium_not_running).",+   "input": {+    "background": "bool, default TRUE: remember the user's foreground window and restore it once Altium is up. Pass false only when the user asked to SEE Altium come up.",+    "install": "optional: develop | designer | a folder name (AD26, ADDevelop) | a path, when several Altiums are installed",+    "waitSec": "number, default 180"+   },+   "longRunning": false,+   "name": "altium_launch",+   "output": {+    "alreadyRunning": "bool",+    "foregroundReturnedTo": "hwnd|null",+    "licence": "{running, licensed, state, adVersion, edition, hwnd, pid}",+    "startedInSec": "float",+    "windowEtiquette": "background once the user's window is handed back; foreground only with background:false"+   },+   "pitfalls": [+    "Altium foregrounds ITSELF at startup and offers no switch for it, so the window jumps over the user's work for a few seconds even with background:true \u2014 the bridge can only hand the focus back afterwards. Call it once; never in a retry loop.",+    "errorCode host_app_not_installed -> altium_install. launch_timeout = no main window after waitSec.",+    "A 'Workspace Unavailable' box at launch (altium_dialog_log) means another running Altium holds the seat: this one is view-only and opens fail with view_only_no_author_seat."+   ],+   "related": [+    "altium_readiness",+    "altium_ensure_licensed",+    "altium_close",+    "altium_install"+   ],+   "statusVerb": null,+   "summary": "Start Altium (the newest numbered install, or the one install names) and wait for its main window, then hand the user's own window back. Answers alreadyRunning when one is up.",+   "timeoutSeconds": 240+  },+  {+   "example": {+    "dryRun": true+   },+   "hint": "This is the manifest's uninstall hook; nothing to call afterwards. To revoke the Altium 365 grant remotely call altium_a365_logout BEFORE this.",+   "input": {+    "dryRun": "bool - report only, delete nothing"+   },+   "longRunning": false,+   "name": "altium_uninstall",+   "output": {+    "dryRun": "bool",+    "note": "string",+    "removed": "[path]"+   },+   "pitfalls": [+    "A resident agent is stopped even with dryRun:true; only the delete is skipped."+   ],+   "related": [+    "altium_a365_logout",+    "altium_agent_stop"+   ],+   "statusVerb": null,+   "summary": "Remove the bridge's own cache directory (agent workspaces, rendered agent scripts, the Altium 365 token file). Altium Designer and the user's designs are never touched.",+   "timeoutSeconds": 30+  },+  {+   "example": {+    "limit": 10+   },+   "hint": "Read it after a surprise: enginePreviouslyParked on a reply, an agent_script_error, or an open that failed view-only ('Workspace Unavailable' shows here). A dialog that reached the user and is NOT listed is a catalog gap: report it.",+   "input": {+    "limit": "int, default 25"+   },+   "longRunning": false,+   "name": "altium_dialog_log",+   "output": {+    "reaped": "[{title, category, pressed, ok, note, at}]",+    "running": "bool"+   },+   "pitfalls": [+    "An in-memory ring of the last 200 events: a bridge restart clears it.",+    "Save prompts and unknown dialogs never appear here as pressed: the reaper does not touch them."+   ],+   "related": [+    "altium_check_dialogs",+    "altium_status",+    "altium_agent_recover"+   ],+   "statusVerb": null,+   "summary": "What the standing dialog reaper (and the bridge's own recoveries) pressed, newest last: nuisance boxes, script-error boxes, discarded save prompts, silent engine halts cleared.",+   "timeoutSeconds": 10+  },+  {+   "example": {},+   "hint": "After success altium_eval / altium_run_command / altium_state go through the agent in milliseconds. Do NOT use altium_run_script while it is up.",+   "input": {+    "waitSec": "int, default 45 - how long to wait for the first heartbeat"+   },+   "longRunning": false,+   "name": "altium_agent_start",+   "output": {+    "alreadyRunning": "bool",+    "generation": "string - AdomAgent_<digest>",+    "install": "object - the rendered script",+    "startedInSec": "float",+    "strayHelpersKilled": "[pid]"+   },+   "pitfalls": [+    "The bootstrap ACTIVATES Altium for a moment (it opens the .PrjScr in the script editor); the bridge hands the user's foreground window back.",+    "no_heartbeat = the engine halted behind a script Error modal: altium_check_dialogs, then altium_agent_recover.",+    "The loop exits by itself 15 s after the last request (while it runs Altium ignores the user's clicks, #27); the next verb restarts it in 2.5-4 s. Call altium_agent_stop when you hand Altium back to the user."+   ],+   "related": [+    "altium_agent_status",+    "altium_agent_stop",+    "altium_agent_recover",+    "altium_eval"+   ],+   "statusVerb": null,+   "summary": "Bootstrap the resident DelphiScript agent inside Altium (renders the script, runs it through the -R lane, waits for the heartbeat). Optional: every agent-backed verb does this on demand.",+   "timeoutSeconds": 120+  },+  {+   "example": {},+   "hint": "Call it when you hand Altium back to the user: while the loop is resident Altium ignores their clicks (it also stops by itself 15 s after the last verb). Needed before altium_run_script (the -R lanes refuse while a loop is resident). altium_close stops every workspace's loop itself. The next agent-backed verb restarts the agent. Clears the 'Adom is driving' taskbar badge.",+   "input": {},+   "longRunning": false,+   "name": "altium_agent_stop",+   "output": {+    "stoppedInSec": "float",+    "wasRunning": "bool"+   },+   "pitfalls": [+    "'the loop did not exit' -> altium_agent_recover.",+    "With several Altiums running pass instance: to stop the right loop."+   ],+   "related": [+    "altium_agent_start",+    "altium_agent_status",+    "altium_run_script",+    "altium_close"+   ],+   "statusVerb": null,+   "summary": "Stop the selected instance's resident agent loop (drops a stop file and waits up to 12 s for the heartbeat to go stale).",+   "timeoutSeconds": 20+  },+  {+   "example": {},+   "hint": "alive with responsive:false = the loop is busy inside a long Altium process: wait, do not start a second agent. Not alive: call the verb you need, it bootstraps.",+   "input": {},+   "longRunning": false,+   "name": "altium_agent_status",+   "output": {+    "alive": "bool - selected instance",+    "heartbeat": "{ageSec, tick, ...} | null",+    "liveWorkspaces": "[{workspace, heartbeatAgeSec}]",+    "residentAnywhere": "bool",+    "responsive": "bool",+    "workspace": "path"+   },+   "pitfalls": [+    "residentAnywhere:true with alive:false is a freeze-on-close hazard: altium_close tears every loop down before the app exits."+   ],+   "related": [+    "altium_agent_start",+    "altium_agent_recover",+    "altium_readiness"+   ],+   "statusVerb": null,+   "summary": "Is the agent loop alive and responsive, in which workspace, and is any loop resident anywhere? Read-only; never starts the agent.",+   "timeoutSeconds": 10+  },+  {+   "example": {+    "reason": "agent_timeout on read_board"+   },+   "hint": "Use after agent_timeout / no_heartbeat / instance_busy, or when Altium sits on AdomAgent_*.pas in its debugger. Then call your verb again: it restarts the agent. Never ask the user to click.",+   "input": {+    "reason": "string - optional, recorded in steps"+   },+   "longRunning": false,+   "name": "altium_agent_recover",+   "output": {+    "steps": "[{reason} | {dialog, pressed} | {editScriptStop, exited, sec} | busy-notice reap]"+   },+   "pitfalls": [+    "A modal orphan (main window disabled, no dialog on screen) answers nothing: recover refuses with altium_modal_orphan instead of firing -R hand-offs that all bounce off 'another instance is busy'. Escape it with altium_close {discardChanges:true}.",+    "It presses OK on EVERY Altium dialog it finds, not only the script Error box: altium_check_dialogs first if a save prompt may be up.",+    "An agent_script_error reply was ALREADY recovered by the bridge (autoRecovered): do not recover again, and do not resend the same command - it will halt again."+   ],+   "related": [+    "altium_check_dialogs",+    "altium_agent_start",+    "altium_dialog_log",+    "altium_selftest_script_error"+   ],+   "statusVerb": null,+   "summary": "Un-halt the script engine: clear the PCB transaction, press OK on open Altium dialogs, send EditScript:Stop, reap the busy notice and delete stale request files.",+   "timeoutSeconds": 60+  },+  {+   "example": {+    "prjscr": "C:/scripts/MyTool.PrjScr",+    "proc": "Main"+   },+   "hint": "While the agent is resident use altium_eval / altium_run_command instead (or altium_agent_stop first). Scripts return nothing: have yours write a file and read that.",+   "input": {+    "prjscr": "absolute path to a .PrjScr on this machine (required)",+    "proc": "string, default 'Main'",+    "timeout": "seconds, default 60"+   },+   "longRunning": false,+   "name": "altium_run_script",+   "output": {+    "arg": "string - the -R argument sent",+    "helperSec": "float",+    "modalsPreempted": "[{title, body}] - bridge-caused boxes cleared in flight"+   },+   "pitfalls": [+    "Refused with agent_resident while ANY agent loop is alive: a second script raises 'Another script executing now' on the user's screen.",+    "ONE SHOT: instance_busy means the running instance did not accept the request. Do not retry in a loop; each attempt costs the user a busy notice."+   ],+   "related": [+    "altium_eval",+    "altium_run_command",+    "altium_agent_stop",+    "altium_agent_recover"+   ],+   "statusVerb": null,+   "summary": "One shot: run a procedure of YOUR OWN .PrjScr through Altium's -R command line. Only for when the agent is NOT resident.",+   "timeoutSeconds": 60+  },+  {+   "example": {+    "query": "zoom",+    "server": "PCB"+   },+   "hint": "Run a hit with altium_run_command {command, params}; hits[].params shows the Name=Value pairs Altium's own menus pass.",+   "input": {+    "limit": "int, default 50",+    "query": "string - substring over command, captions, descriptions and params",+    "server": "string - e.g. PCB, Sch, WorkspaceManager"+   },+   "longRunning": false,+   "name": "altium_list_commands",+   "output": {+    "count": "int",+    "hits": "[{command, server, captions, shortcuts, params, descriptions}]",+    "servers": "{server: count}",+    "total": "int - distinct commands"+   },+   "pitfalls": [+    "Reads files, not Altium: works with Altium closed but answers host_app_not_installed without an install.",+    "Being listed does not make a process safe: some open a modal that blocks the agent (PCB:DesignRuleCheck - use altium_run_drc)."+   ],+   "related": [+    "altium_run_command",+    "altium_eval"+   ],+   "statusVerb": null,+   "summary": "Search Altium's process-launcher catalog (about 1,100 Server:Command entries parsed from the install's System/*.rcs files) by text and/or server.",+   "timeoutSeconds": 30+  },+  {+   "example": {+    "filePath": "C:/proj/board.PcbDoc"+   },+   "hint": "Then the verb for that document: altium_read_board / altium_read_schematic / altium_list_library. status:in_progress -> poll altium_status or altium_window_info until it is current.",+   "input": {+    "filePath": "absolute Windows path (alias: path)",+    "timeout": "seconds; the wait is capped at 45"+   },+   "longRunning": false,+   "name": "altium_open_document",+   "output": {+    "data": "{path, objectKind, kind, shown: bool, lane}",+    "roundtripMs": "int",+    "status": "'in_progress' when Altium is still opening it",+    "windowEtiquette": "background"+   },+   "pitfalls": [+    "Only .PcbDoc .SchDoc .PcbLib .SchLib .OutJob .Pcb3DLib .Prj* .LibPkg .txt .pas .rep are accepted; anything else is refused as unsupported_document (Altium would raise a 'File type not recognised' modal).",+    "success:true with data.shown:false on a non-project means the document did NOT open (this verb does not turn that into an error). In a view-only Altium that is every new document: see view_only_no_author_seat."+   ],+   "related": [+    "altium_activate_document",+    "altium_close_document",+    "altium_state",+    "altium_show_2d_board"+   ],+   "statusVerb": null,+   "summary": "Open a document or project by path through the agent and show it without focusing it. An already-open document is activated, not reloaded.",+   "timeoutSeconds": 60+  },+  {+   "example": {+    "filePath": "C:/proj/board.PrjPcb"+   },+   "hint": "Then altium_compile {projectPath} for ERC, or altium_open_document one of its sheets. altium_state lists the project's documents.",+   "input": {+    "filePath": "absolute Windows path (alias: path)",+    "timeout": "seconds; the wait is capped at 45"+   },+   "longRunning": false,+   "name": "altium_open_project",+   "output": {+    "data": "{path, objectKind: 'Project', shown: false, note}",+    "roundtripMs": "int",+    "windowEtiquette": "background"+   },+   "pitfalls": [+    "data.shown:false is NORMAL for a project: projects have no document view."+   ],+   "related": [+    "altium_open_document",+    "altium_compile",+    "altium_show_project",+    "altium_state"+   ],+   "statusVerb": null,+   "summary": "Open a project (.PrjPcb / .PrjScr / .PrjMbd / .PrjHar / .LibPkg) by path. Same handler as altium_open_document.",+   "timeoutSeconds": 60+  },+  {+   "example": {+    "path": "C:/proj/tof.SchDoc"+   },+   "hint": "altium_save {filePath} first if anything is worth keeping. After re-running altium_place_on_sch / altium_test_board to the same path, close the stale document before altium_show_*.",+   "input": {+    "path": "absolute path of the open document (required)"+   },+   "longRunning": false,+   "name": "altium_close_document",+   "output": {+    "data": "{closed: bool, path} | {closed: false, note: 'not open: <path>'}",+    "roundtripMs": "int"+   },+   "pitfalls": [+    "Unsaved edits are DISCARDED without a prompt.",+    "success:true with closed:false means the document was not open."+   ],+   "related": [+    "altium_save",+    "altium_open_document",+    "altium_state"+   ],+   "statusVerb": null,+   "summary": "Close ONE open document by path through its document handle. Never saves.",+   "timeoutSeconds": 30+  },+  {+   "example": {+    "path": "C:/proj/board.PcbDoc"+   },+   "hint": "Activate the .PcbDoc, then altium_read_board; activate the .SchDoc, then altium_read_schematic. After a demo or a show_* call, activate the user's own document again.",+   "input": {+    "path": "full path of the document (aliases: document, name)",+    "timeout": "seconds; the wait is capped at 45"+   },+   "longRunning": false,+   "name": "altium_activate_document",+   "output": {+    "data": "{path, objectKind, kind, shown}",+    "lane": "agent",+    "status": "'in_progress' when still opening",+    "windowEtiquette": "background"+   },+   "pitfalls": [+    "Needs the FULL path with a supported extension; a bare document name is refused.",+    "It is the same agent 'open': a path that is not open yet gets opened."+   ],+   "related": [+    "altium_open_document",+    "altium_state",+    "altium_read_board",+    "altium_read_schematic"+   ],+   "statusVerb": null,+   "summary": "Make a document current - the quiet lane (one in-process agent call, no cursor, no OS foreground). Board and schematic read verbs act on the CURRENT document.",+   "timeoutSeconds": 60+  },+  {+   "example": {},+   "hint": "Use verbTimes.p90 to choose a timeout for a slow verb on THIS machine, and active[] to see what a long call is doing. Never touches Altium.",+   "input": {},+   "longRunning": false,+   "name": "altium_progress",+   "output": {+    "active": "[progress blocks of phases in flight]",+    "history": "{phase: {warm|cold: {samples, p50, p90}}}",+    "verbTimes": "{verb: {count, p50, p90, max, failures}} - p90 descending, top 40"+   },+   "pitfalls": [],+   "related": [+    "altium_status",+    "altium_describe"+   ],+   "statusVerb": null,+   "summary": "The bridge's timing book: phases in flight right now (percent/ETA blocks ab paints on the taskbar), measured phase history and per-verb run statistics.",+   "timeoutSeconds": 10+  },+  {+   "example": {},+   "hint": "Start here on an unknown board, then altium_review_board for findings or altium_component_data / altium_net_connections for detail.",+   "input": {+    "limit": "int, default 2000",+    "raw": "bool - return the unshaped agent payload",+    "sections": "string csv - optional"+   },+   "longRunning": false,+   "name": "altium_board_summary",+   "output": {+    "roundtripMs": "int",+    "summary": "{board, components, componentsTop, componentsBottom, nets, copperLayersInUse, copperLayersInUseNames, layersWithPrimitives, copperLayersAddressable, rules, rulesEnabled}"+   },+   "pitfalls": [+    "Needs a .PcbDoc current (altium_activate_document).",+    "copperLayersAddressable is what Altium CAN address (top + 30 mid + 16 planes + bottom), not this board's stack: report copperLayersInUse."+   ],+   "related": [+    "altium_read_board",+    "altium_layers_in_use",+    "altium_rules_by_kind",+    "altium_review_board"+   ],+   "statusVerb": null,+   "summary": "One-screen summary of the current board, shaped from one read_board round trip: component counts by side, nets, copper layers actually in use, rule counts.",+   "timeoutSeconds": 120+  },+  {+   "example": {},+   "hint": "Use it to answer 'what clearance/width does this board enforce'. altium_read_rules gives the flat list including disabled rules; altium_run_drc checks the board against them.",+   "input": {+    "limit": "int, default 2000",+    "sections": "string csv - optional"+   },+   "longRunning": false,+   "name": "altium_rules_by_kind",+   "output": {+    "board": "path",+    "roundtripMs": "int",+    "rulesByKind": "{KindName: [{name, kind, enabled, priority, scope1, scope2, comment}]}"+   },+   "pitfalls": [+    "Needs a .PcbDoc current.",+    "Disabled rules are dropped; a rule kind the bridge has no name for appears as 'kind <n>'."+   ],+   "related": [+    "altium_read_rules",+    "altium_run_drc",+    "altium_board_summary"+   ],+   "statusVerb": null,+   "summary": "The current board's ENABLED design rules grouped by rule kind (Clearance, Width, RoutingVias, ...), each group sorted by priority. Shaped from one read_board round trip.",+   "timeoutSeconds": 120+  },+  {+   "example": {},+   "hint": "The honest answer to 'how many layers is this board'. altium_set_layer_visibility {layerId} to isolate one, then altium_show_2d_board for a frame.",+   "input": {+    "limit": "int, default 2000",+    "sections": "string csv - optional"+   },+   "longRunning": false,+   "name": "altium_layers_in_use",+   "output": {+    "board": "path",+    "copperLayersInUse": "[{layerId, name, primitives}]",+    "layersWithPrimitives": "[{layerId, name, primitives}]",+    "roundtripMs": "int"+   },+   "pitfalls": [+    "Needs a .PcbDoc current.",+    "A floor, not the stack: an empty inner plane carries no primitives and is invisible here."+   ],+   "related": [+    "altium_board_summary",+    "altium_layers",+    "altium_copper_layers",+    "altium_set_layer_visibility"+   ],+   "statusVerb": null,+   "summary": "Which layers of the current board actually carry primitives: copper layers with copper (densest first) and every layer with anything on it.",+   "timeoutSeconds": 120+  },+  {+   "example": {},+   "hint": "Then altium_cross_check {board, sheet} to compare against the layout, or altium_schematic_data {designator} for one part's pins.",+   "input": {+    "limit": "int, default 500",+    "params": "bool, default true - include component parameters",+    "sections": "string csv, default 'components'"+   },+   "longRunning": false,+   "name": "altium_read_schematic",+   "output": {+    "components": "[{designator, libReference, comment, ...}]",+    "count": "int",+    "designators": "[string]",+    "document": "string",+    "roundtripMs": "int"+   },+   "pitfalls": [+    "The CURRENT sheet only: a hierarchical parent holds sheet symbols, not parts. Read each leaf sheet.",+    "Needs a .SchDoc current; 'no current schematic' -> altium_open_document / altium_show_schematic."+   ],+   "related": [+    "altium_schematic_data",+    "altium_cross_check",+    "altium_show_schematic",+    "altium_activate_document"+   ],+   "statusVerb": null,+   "summary": "Components on the CURRENT schematic sheet (designator, library reference, comment, position, parameters) in one agent round trip.",+   "timeoutSeconds": 90+  },+  {+   "example": {+    "board": "C:/proj/board.PcbDoc",+    "sheet": "C:/proj/main.SchDoc"+   },+   "hint": "Pass BOTH paths: with neither, one of the two reads has no current document and the call fails at stage board or sheet. Follow differences with altium_component_data / altium_schematic_data.",+   "input": {+    "board": "path to the .PcbDoc - optional, else the current board",+    "sheet": "path to the .SchDoc - optional, else the current sheet",+    "timeout": "seconds per step, default 45"+   },+   "longRunning": false,+   "name": "altium_cross_check",+   "output": {+    "board": "path",+    "boardComponents": "int",+    "matched": "int",+    "onBoardNotOnSheet": "[designator]",+    "onSheetNotOnBoard": "[designator]",+    "sameProject": "bool | null - same folder",+    "schematicComponents": "int",+    "sheet": "string"+   },+   "pitfalls": [+    "The schematic side is ONE sheet: on a hierarchical design run it per leaf sheet before calling anything missing.",+    "_warning means sheet and board sit in different folders, probably different projects: every difference is then meaningless.",+    "With paths given it leaves the SHEET current; altium_activate_document the board again before board verbs."+   ],+   "related": [+    "altium_read_board",+    "altium_read_schematic",+    "altium_review_board"+   ],+   "statusVerb": null,+   "summary": "Compare one schematic sheet's designators with a board's. Altium holds ONE current document, so the verb activates the board, reads it, then activates the sheet and reads that.",+   "timeoutSeconds": 120+  },+  {+   "example": {},+   "hint": "Then altium_component_data {designator} or altium_component_pins {designator}.",+   "input": {},+   "longRunning": false,+   "name": "altium_list_designators",+   "output": {+    "data": "{count, designators: [string]}",+    "roundtripMs": "int"+   },+   "pitfalls": [+    "Needs a .PcbDoc current: 'no current PCB board' carries the _hint to open one."+   ],+   "related": [+    "altium_component_data",+    "altium_component_pins",+    "altium_read_board"+   ],+   "statusVerb": null,+   "summary": "Every component designator on the current board.",+   "timeoutSeconds": 90+  },+  {+   "example": {+    "designator": "U1"+   },+   "hint": "Then altium_component_pins {designator} for pad-level nets, or altium_footprint_primitives {designator} for the geometry.",+   "input": {+    "designator": "string - optional; omit for every component",+    "limit": "int, default 500"+   },+   "longRunning": false,+   "name": "altium_component_data",+   "output": {+    "data": "{component: {designator, pattern, comment, layerId, bottom, xMil, yMil, rotation, wMil, hMil, pads, nets}} | {count, components: [..]}",+    "roundtripMs": "int"+   },+   "pitfalls": [+    "Needs a .PcbDoc current.",+    "Units are mils; the designator match ignores case. limit only applies to the all-components form."+   ],+   "related": [+    "altium_list_designators",+    "altium_component_pins",+    "altium_footprint_primitives"+   ],+   "statusVerb": null,+   "summary": "Placement data for one board component, or for all of them: footprint pattern, comment, layer/side, position, rotation, size, pad count and the nets on its pads.",+   "timeoutSeconds": 90+  },+  {+   "example": {+    "designator": "U1"+   },+   "hint": "Use pins[].net with altium_net_connections {net} to follow a signal across the board.",+   "input": {+    "designator": "string (required), e.g. U1"+   },+   "longRunning": false,+   "name": "altium_component_pins",+   "output": {+    "data": "{designator, count, pins: [{name, net, xMil, yMil, layerId, wMil, hMil, holeMil}]}",+    "roundtripMs": "int"+   },+   "pitfalls": [+    "Needs a .PcbDoc current.",+    "holeMil 0 = an SMD pad."+   ],+   "related": [+    "altium_component_data",+    "altium_net_connections",+    "altium_footprint_primitives"+   ],+   "statusVerb": null,+   "summary": "Every pad of one board component with its net, position, layer, size and hole.",+   "timeoutSeconds": 90+  },+  {+   "example": {},+   "hint": "Then altium_net_connections {net} for the pads of one net.",+   "input": {},+   "longRunning": false,+   "name": "altium_list_nets",+   "output": {+    "data": "{count, nets: [{name, pads}]}",+    "roundtripMs": "int"+   },+   "pitfalls": [+    "Needs a .PcbDoc current.",+    "Pad counts are tallied over pads: a net with no pads on it does not appear."+   ],+   "related": [+    "altium_net_connections",+    "altium_read_board"+   ],+   "statusVerb": null,+   "summary": "Every net on the current board with its pad count.",+   "timeoutSeconds": 90+  },+  {+   "example": {+    "net": "GND"+   },+   "hint": "Pass net on anything but a small board. The span string is 'minX,minY,maxX,maxY,pads' in mils: a quick measure of how far a net travels.",+   "input": {+    "limit": "int, default 4000 - caps pads",+    "net": "string - optional net name"+   },+   "longRunning": false,+   "name": "altium_net_connections",+   "output": {+    "data": "{count, pads: [{net, pad: 'U1.3', xMil, yMil}], nets: [{net, minXminYmaxXmaxYpads}]}",+    "roundtripMs": "int"+   },+   "pitfalls": [+    "Needs a .PcbDoc current.",+    "limit truncates silently: count is what was returned, not what exists."+   ],+   "related": [+    "altium_list_nets",+    "altium_component_pins"+   ],+   "statusVerb": null,+   "summary": "Every pad on the board with its net and owner (Designator.Pad), plus a bounding span and the airline length (minimum spanning tree over its pads, airlineMm) per net. net filters to one net.",+   "timeoutSeconds": 120+  },+  {+   "example": {},+   "hint": "ids feed altium_set_layer_visibility {layerId}. For what is actually used, altium_layers_in_use.",+   "input": {},+   "longRunning": false,+   "name": "altium_layers",+   "output": {+    "data": "{layers: [{name, id, kind, displayed}]}",+    "roundtripMs": "int"+   },+   "pitfalls": [+    "Needs a .PcbDoc current.",+    "The fixed layer set, not the stackup: thickness and dielectric detail are not read."+   ],+   "related": [+    "altium_copper_layers",+    "altium_layers_in_use",+    "altium_set_layer_visibility"+   ],+   "statusVerb": null,+   "summary": "The current board's layer set with ids, names, kind and whether each is displayed.",+   "timeoutSeconds": 90+  },+  {+   "example": {},+   "hint": "Do not count these as the board's layers: altium_layers_in_use says which carry copper.",+   "input": {},+   "longRunning": false,+   "name": "altium_copper_layers",+   "output": {+    "data": "{layers: [{name, id, kind, displayed}]}",+    "roundtripMs": "int"+   },+   "pitfalls": [+    "Needs a .PcbDoc current.",+    "A capability list (the same on any board), not the stack in use."+   ],+   "related": [+    "altium_layers",+    "altium_layers_in_use"+   ],+   "statusVerb": null,+   "summary": "Every copper layer Altium can address on the current board (top, mid 1-30, planes 1-16, bottom) with names and visibility.",+   "timeoutSeconds": 90+  },+  {+   "example": {},+   "hint": "Open the board first (altium_show_2d_board). dk1000 is the dielectric constant times 1000 (4200 = 4.2).",+   "input": {},+   "longRunning": false,+   "name": "altium_layer_stack",+   "output": {+    "data": "{copperLayers, totalUm, layers: [{name, copperUm, dielectricUm (the dielectric BELOW this layer), dk1000 (dielectric constant x 1000), material}]}"+   },+   "pitfalls": [+    "Solder mask and overlay layers are not part of this list; the copper and the dielectric under each are."+   ],+   "related": [+    "altium_layers",+    "altium_copper_layers",+    "altium_read_rules"+   ],+   "statusVerb": null,+   "summary": "The CURRENT board's stack-up: copper layers in order with copper thickness and the dielectric under each (height, constant, material), and the total thickness. For impedance, current capacity and fab quotes.",+   "timeoutSeconds": 60+  },+  {+   "example": {},+   "hint": "altium_rules_by_kind groups the enabled ones under readable kind names; altium_run_drc checks the board against them.",+   "input": {},+   "longRunning": false,+   "name": "altium_read_rules",+   "output": {+    "data": "{count, rules: [{name, kind: int, enabled, priority, scope1, scope2, comment, descriptor}]}",+    "roundtripMs": "int"+   },+   "pitfalls": [+    "Needs a .PcbDoc current.",+    "kind is Altium's TRuleKind ORDINAL; the values are in descriptor, as Altium words them."+   ],+   "related": [+    "altium_rules_by_kind",+    "altium_run_drc"+   ],+   "statusVerb": null,+   "summary": "Every design rule on the current board, flat, disabled ones included: name, kind ordinal, enabled, priority, both scope expressions, comment, and Altium's descriptor with the rule's VALUES (e.g. 'Clearance Constraint (Gap=6mil) (All),(All)').",+   "timeoutSeconds": 90+  },+  {+   "example": {+    "displayed": false,+    "layerId": 1+   },+   "hint": "Then altium_show_2d_board {filePath} for a frame of the result. Restore what you hid: visibility is view state the user keeps seeing.",+   "input": {+    "displayed": "bool, default true",+    "layerId": "int (required) - an id from altium_layers"+   },+   "longRunning": false,+   "name": "altium_set_layer_visibility",+   "output": {+    "data": "{layerId, name, displayed}",+    "roundtripMs": "int"+   },+   "pitfalls": [+    "Needs a .PcbDoc current.",+    "Only pass ids that altium_layers returned: the id is not range-checked before the layer object is read."+   ],+   "related": [+    "altium_layers",+    "altium_layers_in_use",+    "altium_show_2d_board"+   ],+   "statusVerb": null,+   "summary": "Show or hide one layer of the current board (by layer id) and refresh the view and the layer tabs.",+   "timeoutSeconds": 60+  },+  {+   "example": {+    "expectPath": "C:/designs/buck/buck.PcbDoc"+   },+   "hint": "Layers use KiCad names (F.Cu, B.Cu, In<n>.Cu; *.Cu = a multi-layer pad) with Altium's layerId alongside. Coordinates are Altium's own frame (y up), in mm. This is the read AI Flow's Altium lane calls before and after every edit.",+   "input": {+    "expectPath": "string (optional) - refuse with wrong_board unless this .PcbDoc is the current board"+   },+   "longRunning": false,+   "name": "altium_routing_state",+   "output": {+    "boardId": "string (the .PcbDoc path)",+    "components": "[{ref, x, y, rotation, side, pattern}]",+    "counts": "{tracks, arcs, vias, pads, components, polygons, nets}",+    "outline": "[[x,y]]",+    "pads": "[{ref, name, x, y, sizeX, sizeY, rotation, layer, drill, net}]",+    "polygons": "[{net, layer, points[[x,y]]}]",+    "revision": "string - pass as expectedRevision to the edit verbs",+    "tracks": "[{start{x,y}, end{x,y}, width, layer, net}]",+    "unconnected": "int",+    "vias": "[{position{x,y}, size, drill, layers[2], net}]"+   },+   "pitfalls": [+    "Needs a .PcbDoc current (altium_show_2d_board {filePath}).",+    "Footprint-internal tracks and arcs are left out; only free copper is listed.",+    "unconnected counts Altium's ratsnest lines: re-read after an edit for a fresh count."+   ],+   "related": [+    "altium_read_board",+    "altium_net_connections",+    "altium_footprint_primitives"+   ],+   "statusVerb": null,+   "summary": "The WHOLE current board as data, in mm: tracks, arcs, vias, pads (with owner and net), components (ref, x, y, rotation, side), polygon pours, the outline, the unrouted (ratsnest) count, and a revision hash.",+   "timeoutSeconds": 180+  },+  {+   "example": {+    "net": "VOUT",+    "paths": [+     [+      {+       "pad": "L1.2"+      },+      [+       120.0,+       90.0+      ],+      {+       "pad": "C6.1"+      }+     ]+    ],+    "width": 0.5+   },+   "hint": "Coordinates are mm in Altium's frame, the same as altium_routing_state. There is no clearance check here yet: run your own DRC (AI Flow's gate) or altium_run_drc after.",+   "input": {+    "boardId": "string (optional) - refused with board_mismatch unless this is the current board",+    "expectPath": "string (optional)",+    "expectedRevision": "string (optional) - the revision you last read; refused with stale_board if the board changed",+    "layer": "F.Cu|B.Cu|In<n>.Cu or an Altium layer id (default F.Cu)",+    "net": "string (required)",+    "paths": "[[waypoint,...]] - a waypoint is [x,y], {x,y,layer} or {pad:\"U1.3\"}",+    "viaDrill": "mm (default 0.3)",+    "viaSize": "mm (default 0.6)",+    "width": "mm (default 0.25)"+   },+   "longRunning": false,+   "name": "altium_route_net",+   "output": {+    "applied": "int",+    "counts": "{tracks, vias, ...}",+    "failed": "int",+    "results": "[ok|err:...] per segment/via",+    "revision": "string - the board after the edit",+    "state": "the full altium_routing_state after",+    "unconnected": "int"+   },+   "pitfalls": [+    "Needs the board current.",+    "A net name must already exist on the board (nets come from the schematic / pads)."+   ],+   "related": [+    "altium_routing_state",+    "altium_place_vias",+    "altium_remove_items",+    "altium_ripup_nets"+   ],+   "statusVerb": null,+   "summary": "Route one net as tracks, segment by segment; a waypoint on another layer drops a via there. One undo step, read back.",+   "timeoutSeconds": 300+  },+  {+   "example": {+    "parts": [+     {+      "ref": "U1",+      "rotation": 90,+      "x": 112.0,+      "y": 92.0+     }+    ]+   },+   "hint": "Rotation is absolute, not a delta.",+   "input": {+    "boardId": "string (optional) - refused with board_mismatch unless this is the current board",+    "expectPath": "string (optional)",+    "expectedRevision": "string (optional) - the revision you last read; refused with stale_board if the board changed",+    "parts": "[{ref, x, y, rotation}] - mm and degrees, Altium's frame; or {ref, dx, dy} to move by an offset; rotation kept when left out"+   },+   "longRunning": false,+   "name": "altium_move_parts",+   "output": {+    "mismatches": "[{ref, asked, got}] when a part did not land where asked (errorCode readback_mismatch)",+    "results": "[ok|err:...]",+    "revision": "string"+   },+   "pitfalls": [+    "Moving to the other side of the board is not supported yet."+   ],+   "related": [+    "altium_routing_state",+    "altium_route_net"+   ],+   "statusVerb": null,+   "summary": "Move and rotate placed components; each part is read back to 0.001 mm and the exact rotation. One undo step.",+   "timeoutSeconds": 300+  },+  {+   "example": {+    "vias": [+     {+      "drill": 0.3,+      "net": "GND",+      "size": 0.6,+      "x": 110.0,+      "y": 90.0+     }+    ]+   },+   "hint": "Through vias, top to bottom, on the named net. Heatsink vias under a pad: place them here, then pour or route to them.",+   "input": {+    "boardId": "string (optional) - refused with board_mismatch unless this is the current board",+    "expectPath": "string (optional)",+    "expectedRevision": "string (optional) - the revision you last read; refused with stale_board if the board changed",+    "vias": "[{net, x, y, size, drill}] - mm"+   },+   "longRunning": false,+   "name": "altium_place_vias",+   "output": {+    "counts": "{vias, ...}",+    "results": "[ok|err:...]",+    "revision": "string"+   },+   "pitfalls": [],+   "related": [+    "altium_route_net",+    "altium_routing_state"+   ],+   "statusVerb": null,+   "summary": "Place vias (through, top to bottom). One undo step, read back.",+   "timeoutSeconds": 300+  },+  {+   "example": {+    "vias": [+     {+      "x": 110.0,+      "y": 90.0+     }+    ]+   },+   "hint": "Undo a bad segment or via without touching the rest of the net; altium_ripup_nets clears a whole net.",+   "input": {+    "boardId": "string (optional) - refused with board_mismatch unless this is the current board",+    "expectPath": "string (optional)",+    "expectedRevision": "string (optional) - the revision you last read; refused with stale_board if the board changed",+    "tracks": "[{start{x,y}, end{x,y}, layer}]",+    "vias": "[{position{x,y}}] or [{x,y}]"+   },+   "longRunning": false,+   "name": "altium_remove_items",+   "output": {+    "results": "[ok|err:not found]",+    "revision": "string"+   },+   "pitfalls": [+    "Matching is exact to the internal unit: pass the coordinates you read, not rounded ones."+   ],+   "related": [+    "altium_ripup_nets",+    "altium_routing_state"+   ],+   "statusVerb": null,+   "summary": "Delete specific free tracks and vias, given exactly as altium_routing_state returned them. One undo step.",+   "timeoutSeconds": 300+  },+  {+   "example": {+    "nets": [+     "SW",+     "VOUT"+    ]+   },+   "hint": "Pours are not touched.",+   "input": {+    "boardId": "string (optional) - refused with board_mismatch unless this is the current board",+    "expectPath": "string (optional)",+    "expectedRevision": "string (optional) - the revision you last read; refused with stale_board if the board changed",+    "nets": "[string]"+   },+   "longRunning": false,+   "name": "altium_ripup_nets",+   "output": {+    "results": "[ok:<deleted count>]",+    "revision": "string"+   },+   "pitfalls": [],+   "related": [+    "altium_remove_items",+    "altium_route_net"+   ],+   "statusVerb": null,+   "summary": "Delete every free track, arc and via on each named net. One undo step.",+   "timeoutSeconds": 300+  },+  {+   "example": {+    "cutouts": [+     {+      "layer": "F.Cu",+      "name": "Kelvin tap",+      "points": [+       [+        20,+        2+       ],+       [+        22,+        2+       ],+       [+        22,+        4+       ],+       [+        20,+        4+       ]+      ]+     }+    ],+    "pours": [+     {+      "connect": "relief",+      "layer": "B.Cu",+      "net": "GND",+      "points": [+       [+        0,+        0+       ],+       [+        28,+        0+       ],+       [+        28,+        20+       ],+       [+        0,+        20+       ]+      ],+      "reliefGap": 0.25,+      "reliefWidth": 0.35+     },+     {+      "connect": "direct",+      "layer": "F.Cu",+      "net": "SW",+      "order": 1,+      "points": [+       [+        10,+        5+       ],+       [+        16,+        5+       ],+       [+        16,+        9+       ],+       [+        10,+        9+       ]+      ]+     }+    ]+   },+   "hint": "connect writes a PolygonConnect rule 'AdomPC <name>' scoped IsNamedPolygon('<name>') that outranks the board's own, so a power patch can join its pads solid while the ground pour keeps spokes. Without connect the board's Polygon Connect Style applies (Altium's default is 4 relief spokes of 10 mil, a current neck). Clearance comes from the board's Clearance rule. Dead copper is removed unless removeDead is false.",+   "input": {+    "cutouts": "(optional) [{layer, points:[[x,y]...] mm, name?}] - areas every pour on that layer stays out of (KiCad's pour keepout); landed before the pours",+    "expectedRevision": "string (optional) - refused with stale_board if the board changed since you read it",+    "pours": "[{net, layer (F.Cu|B.Cu|In<n>.Cu), points:[[x,y]...] mm, order?: pour order (1 = poured first), name?: polygon name, connect?: direct|relief|none, reliefWidth?: mm spoke width, reliefGap?: mm air gap (not settable by script on AD 26: Altium keeps 10 mil and notes[] says so), reliefEntries?: 2|4, removeDead?: bool (default true), removeSmallIslands?: bool (default false: connected copper of any size stays, as in KiCad; true = Altium's own 2500 sq mil island filter), minNeck?: mm - copper narrower than this is removed (KiCad's zone min thickness), solidPads?: [[x1,y1,x2,y2]] mm - pads completely inside each rect join THIS pour solid whatever its connect (KiCad's small solid joint zone over a pad; Altium pours nothing for a same-net polygon inside another)}]"+   },+   "longRunning": false,+   "name": "altium_apply_pours",+   "output": {+    "filledAreaByLayerMm2": "{F.Cu: mm2, ...}",+    "results": "[ok:<polygon name>|err:...]",+    "revision": "string",+    "unfilled": "[zone] that poured nothing",+    "zones": "[{net, layer, outline, filledMm2, islands, filled}]"+   },+   "pitfalls": [+    "Needs the board current.",+    "The outline need not repeat its first point.",+    "A name already on the board gets a ' 2' suffix: read the real name from results."+   ],+   "related": [+    "altium_pour_state",+    "altium_repour",+    "altium_remove_pours",+    "altium_routing_state",+    "altium_set_design_rules"+   ],+   "statusVerb": null,+   "summary": "Add solid polygon pours from outlines and pour them, each with its own connect style (solid joints or thermal spokes), plus pour keepouts (polygon cutouts); answers each pour's real poured copper area. One undo step.",+   "timeoutSeconds": 600+  },+  {+   "example": {},+   "hint": "filledMm2 is regions minus holes, computed from Altium's own poured geometry.",+   "input": {},+   "longRunning": false,+   "name": "altium_pour_state",+   "output": {+    "filledAreaByLayerMm2": "{layer: mm2}",+    "unfilled": "[zone]",+    "zones": "[{net, name, layer, pourIndex, outline, filledMm2, islands, filled}]"+   },+   "pitfalls": [+    "Needs the board current.",+    "A shelved or unpoured polygon reads filledMm2 0."+   ],+   "related": [+    "altium_apply_pours",+    "altium_repour"+   ],+   "statusVerb": null,+   "summary": "Every polygon pour with the copper it actually poured: filled area per pour and per layer, and the pours that poured nothing.",+   "timeoutSeconds": 180+  },+  {+   "example": {+    "pours": [+     {+      "net": "GND"+     }+    ]+   },+   "hint": "Use before re-pouring a changed plan; the connect rules of removed pours go with them. altium_ripup_nets clears copper.",+   "input": {+    "cutouts": "(optional) \"all\" or [{layer}] - only the cutouts apply_pours landed (named Adom...)",+    "expectedRevision": "string (optional) - refused with stale_board if the board changed since you read it",+    "pours": "(optional) \"all\" or [{net, layer?} | {name} - one pour by the name apply_pours answered]"+   },+   "longRunning": false,+   "name": "altium_remove_pours",+   "output": {+    "cutoutsRemoved": "int",+    "removed": "int",+    "revision": "string",+    "rulesRemoved": "int - AdomPC connect rules whose pour is gone"+   },+   "pitfalls": [],+   "related": [+    "altium_apply_pours",+    "altium_ripup_nets"+   ],+   "statusVerb": null,+   "summary": "Delete polygon pours by net (and layer), or all of them, and the pour keepouts (cutouts) apply_pours made. Tracks and vias are untouched.",+   "timeoutSeconds": 300+  },+  {+   "example": {},+   "hint": "Run after moving parts or copper that pours flow around. Altium evaluates rule queries through its scripting system, so a repour from inside the resident agent ignores every scoped rule (a pour's own connect style, net-class clearances); this verb stops the agent, repours, and starts it again (a few seconds). apply_pours does the same on its own when it wrote connect rules.",+   "input": {+    "inScript": "bool (default false) - repour from inside the agent instead (scoped rules then match nothing)"+   },+   "longRunning": false,+   "name": "altium_repour",+   "output": {+    "filledAreaByLayerMm2": "{...}",+    "run": "{helperExited, seconds, agentStart}",+    "unfilled": "[...]",+    "zones": "[...]"+   },+   "pitfalls": [+    "Needs the board current.",+    "The agent restarts: a verb sent meanwhile waits for it."+   ],+   "related": [+    "altium_pour_state",+    "altium_apply_pours"+   ],+   "statusVerb": null,+   "summary": "Repour every polygon on the board (Altium's Repour All) and answer the copper after. Runs outside the script engine so scoped rules apply.",+   "timeoutSeconds": 600+  },+  {+   "example": {+    "full": true+   },+   "hint": "Without full, this reads online DRC's live violations (fast). full:true runs the batch check through Altium's DRC dialog first.",+   "input": {+    "full": "bool (default false) - run Altium's batch DRC on this board first"+   },+   "longRunning": false,+   "name": "altium_routing_drc",+   "output": {+    "clean": "bool - no violations and nothing unrouted",+    "errors": "int",+    "unconnected": "int",+    "violations": "[{rule, description, at{x,y} mm}]"+   },+   "pitfalls": [+    "Needs the board current.",+    "violations[] is capped at 500; errors is the real count."+   ],+   "related": [+    "altium_routing_state",+    "altium_run_drc"+   ],+   "statusVerb": null,+   "summary": "Design-rule violations on the current board WITH locations, plus the unrouted (ratsnest) count, as one clean/not-clean answer.",+   "timeoutSeconds": 300+  },+  {+   "example": {+    "libraryPath": "C:/designs/buck/Buck.PcbLib",+    "nets": {+     "GND": [+      "U1.1"+     ]+    },+    "outline": {+     "cornerRadius": 1,+     "rect": [+      0,+      0,+      28,+      20+     ]+    },+    "parts": [+     {+      "footprint": "SOT-23-6",+      "ref": "U1",+      "rotation": 0,+      "x": 12,+      "y": 10+     }+    ],+    "pcbPath": "C:/designs/buck/buck.PcbDoc"+   },+   "hint": "The board AI Flow's Altium project builds from its netlist. Footprint pad names must match the netlist's pin numbers (R1.1 = pad 1 of R1). Coordinates are Altium's frame (y up).",+   "input": {+    "libraryPath": "string (required) - the .PcbLib holding every footprint",+    "nets": "{net: [\"R1.1\", \"U1.3\", ...]}",+    "outline": "{rect:[x0,y0,w,h], cornerRadius?} or {points:[[x,y]...]} (mm)",+    "parts": "[{ref, footprint, x, y, rotation, comment?}] (mm, degrees; x,y is the footprint's own origin)",+    "pcbPath": "string (required) - the .PcbDoc to write"+   },+   "longRunning": false,+   "name": "altium_build_board",+   "output": {+    "counts": "{...}",+    "missingParts": "[ref]",+    "padsWired": "int",+    "placed": "int",+    "revision": "string",+    "unconnected": "int - the ratsnest to route",+    "wrongPins": "[{pin, want, got}]"+   },+   "pitfalls": [+    "The library must open in this Altium; a footprint missing from it refuses the whole build (footprint_not_found).",+    "A new board each call: pcbPath is overwritten."+   ],+   "related": [+    "altium_routing_state",+    "altium_set_outline",+    "altium_install_library"+   ],+   "statusVerb": null,+   "summary": "Build a NEW board from a parts list and a netlist: outline (rounded corners allowed), every footprint from one PcbLib placed by its own origin, pad nets assigned, saved; then read back pin by pin.",+   "timeoutSeconds": 900+  },+  {+   "example": {+    "outline": {+     "cornerRadius": 1,+     "rect": [+      0,+      0,+      28,+      20+     ]+    }+   },+   "hint": "Rounding corners: pass the same rectangle with cornerRadius (1 mm is a common default).",+   "input": {+    "expectedRevision": "string (optional) - refused with stale_board if the board changed",+    "outline": "{rect:[x0,y0,w,h], cornerRadius?} or {points:[[x,y]...]} (mm)"+   },+   "longRunning": false,+   "name": "altium_set_outline",+   "output": {+    "outlineEdges": "[{kind: line|arc, ...}]",+    "revision": "string",+    "vertices": "int"+   },+   "pitfalls": [+    "Needs the board current."+   ],+   "related": [+    "altium_build_board",+    "altium_routing_state",+    "altium_repour"+   ],+   "statusVerb": null,+   "summary": "Reshape the current board's outline (a rectangle with rounded corners, or a polygon). One undo step.",+   "timeoutSeconds": 120+  },+  {+   "example": {+    "clearance": 0.15,+    "edgeClearance": 0.3,+    "hole": {+     "min": 0.2+    },+    "trackWidth": {+     "min": 0.15+    },+    "via": {+     "diameter": {+      "min": 0.45+     },+     "drill": {+      "min": 0.2+     }+    }+   },+   "hint": "AI Flow's `rules use` calls this with the fab pack's numbers (adom/pcb-design-rules). Each kind is written separately, so one this Altium build lacks fails alone.",+   "input": {+    "annularRing": "mm",+    "clearance": "mm",+    "costlier": "with fab: allow JLCPCB's surcharged small vias (0.15/0.25 mm) instead of 0.3/0.5",+    "edgeClearance": "mm - copper to the board edge (the rule it creates is scoped OnCopper; silkscreen keeps its own rules)",+    "fab": "jlcpcb - fill every value left out from JLCPCB's published limits for this board's layers and copper weight",+    "hole": "{min, max?} mm",+    "holeToHole": "mm",+    "polygonConnect": "the board's catch-all polygon connect style: direct|relief|none, or {style, reliefWidth: mm, reliefEntries: 2|4} (per-pour rules from apply_pours keep theirs)",+    "trackWidth": "{min, preferred?, max?} mm",+    "via": "{diameter:{min, preferred?, max?}, drill:{min, preferred?, max?}} mm"+   },+   "longRunning": false,+   "name": "altium_set_design_rules",+   "output": {+    "failed": "[{kind, error}]",+    "landed": "[{kind, rulesWritten}]",+    "rules": "the board's rules after"+   },+   "pitfalls": [+    "Needs the board current.",+    "Every existing rule of a kind gets the values; rule scopes are kept, except an Adom edge rule an earlier bridge scoped to All, which becomes OnCopper (#26)."+   ],+   "related": [+    "altium_read_rules",+    "altium_rules_by_kind",+    "altium_build_board"+   ],+   "statusVerb": null,+   "summary": "Write a fab's design rules into the current board: clearance, track width, routing via style, hole size, annular ring, hole-to-hole and board-edge clearance (mm).",+   "timeoutSeconds": 300+  },+  {+   "example": {+    "gap": 0.3,+    "kind": "clearance",+    "name": "SW node",+    "scope1": "InNet('SW')",+    "scope2": "All"+   },+   "hint": "AI Flow's net clearances (a switch node kept farther from everything) land here. A scope with a typo matches nothing and DRC then passes silently: check it in the rules editor (Test Queries).",+   "input": {+    "comment": "optional",+    "drill": "mm (via)",+    "gap": "mm (clearance; diffpair: the pair's gap)",+    "kind": "clearance|width|length|matched|diffpair|via|polyconnect",+    "max": "mm (width, optional; length: required)",+    "maxDrill": "via, optional",+    "maxGap": "diffpair, optional",+    "maxSize": "via, optional",+    "maxUncoupled": "mm, diffpair, optional",+    "maxWidth": "diffpair, optional",+    "min": "mm (width; length)",+    "minDrill": "via, optional",+    "minGap": "diffpair, optional",+    "minSize": "via, optional",+    "minWidth": "diffpair, optional",+    "name": "string - the rule is named 'Adom <name>'; the same name replaces it",+    "preferred": "mm (width, optional)",+    "reliefEntries": "2 | 4, polyconnect relief",+    "reliefWidth": "mm, polyconnect relief spokes",+    "remove": "bool - delete the Adom rule of this kind and name instead",+    "scope1": "Altium query, e.g. InNet('SW'), InNetClass('DDR_D0'), InDifferentialPair('USB')",+    "scope2": "query (default All; clearance, and polyconnect: the polygon, e.g. InPolygon('GND pour'))",+    "size": "mm (via)",+    "style": "polyconnect: direct | relief | none",+    "tolerance": "mm (matched)",+    "width": "mm (diffpair)"+   },+   "longRunning": false,+   "name": "altium_add_rule",+   "output": {+    "name": "string",+    "priority": "int (1 = first of its kind)"+   },+   "pitfalls": [+    "Needs the board current.",+    "One rule per call."+   ],+   "related": [+    "altium_set_design_rules",+    "altium_read_rules",+    "altium_routing_drc"+   ],+   "statusVerb": null,+   "summary": "Add one SCOPED design rule that set_design_rules' board-wide values cannot express: a net's or class's clearance or width, a length limit, a matched-length group, a differential pair's width and gap, a routing via style, or how a net's pads join a pour (direct, relief, none).",+   "timeoutSeconds": 60+  },+  {+   "example": {+    "libraryPath": "C:\\work\\library\\Buck.SchLib",+    "nets": {+     "VIN": [+      "U1.3"+     ]+    },+    "parts": [+     {+      "block": "core",+      "comment": "TPS54202",+      "ref": "U1",+      "symbol": "TPS54202DDCR"+     }+    ],+    "schPath": "C:\\work\\Buck.SchDoc"+   },+   "hint": "One column per block, each part with a wire stub and a net label on every connected pin; designator above, value below; title block filled. The compiled netlist is the proof: success means it matches the given nets.",+   "input": {+    "drawnBy": "optional",+    "libraryPath": "string - the project .SchLib",+    "nets": "{net: [\"U1.3\", ...]} - pins of parts not on the sheet are ignored",+    "number": "optional",+    "parts": "[{ref, symbol: LibReference in the SchLib, block?: column name (input, core, output...), comment?: value, footprint?: its footprint in pcbLibPath (the part's PCB model link)}]",+    "pcbLibPath": "(optional)",+    "pcbPath": "(optional) the board, listed in the project",+    "projectPath": "(optional) .PrjPcb, default beside the sheet",+    "revision": "optional",+    "schPath": "string - the .SchDoc to write",+    "title": "optional"+   },+   "longRunning": false,+   "name": "altium_build_schematic",+   "output": {+    "compileErrors": "[..]",+    "labels": "int",+    "mismatches": "[{net, missing, extra}]",+    "pinsWithoutNet": "ref.pin;...",+    "placed": "int",+    "project": "path",+    "sch": "path",+    "wires": "int"+   },+   "pitfalls": [+    "The SchLib must hold every symbol (altium_import_part).",+    "Parts are placed unrotated."+   ],+   "related": [+    "altium_schematic_data",+    "altium_compile",+    "altium_build_board"+   ],+   "statusVerb": null,+   "summary": "Draw a design's schematic from its netlist with the project library's symbols, inside a new project (.PrjPcb with the board and libraries), compile it, and check its netlist pin by pin.",+   "timeoutSeconds": 900+  },+  {+   "example": {+    "pcbPath": "C:\\work\\Buck.PcbDoc",+    "projectPath": "C:\\work\\Buck.PrjPcb"+   },+   "hint": "Pull files[] with pull_file and check them against the board (layers, drill hits, every placed part in the pick and place and the BOM) before handing them over. AI Flow's `altium export` does both.",+   "input": {+    "bomColumns": "(optional) [column or parameter name] - default Comment, Description, Designator, Footprint, Quantity, MPN, Manufacturer, Mouser/DigiKey/LCSC Part #",+    "clean": "bool (default true) - empty the output folder first",+    "formats": "[odb, ipc2581] - more fab formats beside the Gerbers (ODB++ folder + .tgz, IPC-2581 .cvg); default Gerbers only",+    "pcbPath": "its .PcbDoc",+    "projectPath": "the .PrjPcb",+    "silkPrep": "bool (default true) - Altium's Silkscreen Preparation first (silk off the pads, text moved where it fits)",+    "silkPrepCheck": "bool (default false) - DRC silk counts before/after in silkPrep",+    "timeout": "seconds, default 300"+   },+   "longRunning": false,+   "name": "altium_export_manufacturing",+   "output": {+    "dialogs": "[..]",+    "elapsedSec": "float",+    "files": "[relative paths]",+    "outjob": "path",+    "outputDir": "path"+   },+   "pitfalls": [+    "The board must be a document of the project.",+    "The BOM is the compiled project's (schematic parts); board-only pads are not in it."+   ],+   "related": [+    "altium_run_outjob",+    "altium_build_schematic",+    "altium_routing_state"+   ],+   "statusVerb": null,+   "summary": "A project's manufacturing set in one call: Gerbers, NC drill, pick and place (CSV, mm) and the BOM, from an OutJob the bridge writes beside the project (composed from this Altium's own OutJob templates, every output on one medium that opens nothing).",+   "timeoutSeconds": 600+  },+  {+   "example": {+    "net": "SW",+    "zoom": true+   },+   "hint": "AI Flow's net walkthrough lights each loaded net in turn. Altium draws a selection in its highlight colour.",+   "input": {+    "margin": "mm, default 1",+    "net": "string (optional) - empty or absent clears",+    "zoom": "bool - frame the net (altium_zoom_to)"+   },+   "longRunning": false,+   "name": "altium_select_net",+   "output": {+    "byKind": "{tracks, arcs, vias, pads, pours, other}",+    "net": "string",+    "selected": "int",+    "zoom": "[l, b, r, t] mm when zoom"+   },+   "pitfalls": [+    "Needs the board current.",+    "Pour regions are counted once, as their polygon."+   ],+   "related": [+    "altium_zoom_to",+    "altium_routing_state",+    "altium_list_nets"+   ],+   "statusVerb": null,+   "summary": "Select one net as a whole on the current board (tracks, arcs, vias, pads, pours) and nothing else, optionally framing it; no net clears the selection.",+   "timeoutSeconds": 120+  },+  {+   "example": {},+   "hint": "The input to a silkscreen plan (AI Flow's `altium silkscreen`), and its check after landing: compare each box with the pads and vias from routing_state.",+   "input": {},+   "longRunning": false,+   "name": "altium_silk_state",+   "output": {+    "components": "[{ref, pattern, x, y, rotation, side, body:[l,b,r,t], name:{text,x,y,size,rotation,shown,box}, comment:{...}}]",+    "lines": "[{side, x1, y1, x2, y2, width, raw}]",+    "texts": "[{side, text, x, y, size, rotation, mirror, box, raw}]"+   },+   "pitfalls": [+    "Needs the board current.",+    "raw is the exact handle apply_silkscreen's remove takes."+   ],+   "related": [+    "altium_apply_silkscreen",+    "altium_routing_state"+   ],+   "statusVerb": null,+   "summary": "The silkscreen as it stands: every part's body box, its designator and comment (text, location, size, rotation, shown, Altium's measured box), and the free overlay texts and lines on both faces.",+   "timeoutSeconds": 180+  },+  {+   "example": {+    "lines": [+     {+      "x1": 1,+      "x2": 5,+      "y1": 1,+      "y2": 1+     }+    ],+    "names": [+     {+      "ref": "U1",+      "size": 0.8,+      "x": 12.0,+      "y": 14.2+     }+    ]+   },+   "hint": "A text's location is its lower left before rotation (for 90 degrees: the box's lower right). Stroke width is a seventh of the height.",+   "input": {+    "comments": "[same] - the value",+    "lines": "[{x1, y1, x2, y2, width?: mm (0.15), side?}]",+    "names": "[{ref, x, y, size, rotation?, show?}] - the designator's own location, mm",+    "remove": "{texts:[raw], lines:[raw]} - silk_state raw handles of earlier free items",+    "texts": "[{text, x, y, size, rotation?, side?: top|bottom}]"+   },+   "longRunning": false,+   "name": "altium_apply_silkscreen",+   "output": {+    "after": "silk_state",+    "applied": "int",+    "failed": "int",+    "results": "[ok|err:...]"+   },+   "pitfalls": [+    "Needs the board current.",+    "Removal matches the raw handle exactly: pass what silk_state answered."+   ],+   "related": [+    "altium_silk_state",+    "altium_routing_drc"+   ],+   "statusVerb": null,+   "summary": "Land a silkscreen plan in one undo step: designators and comments moved, sized and turned (or hidden), free overlay texts and lines added (bottom texts mirrored), earlier ones removed; answers silk_state after.",+   "timeoutSeconds": 600+  },+  {+   "example": {+    "margin": 0.5,+    "net": "SW"+   },+   "hint": "For a close look at a joint or a camera shot of one part or net; altium_run_command PCB:Zoom Action=All frames the whole board.",+   "input": {+    "margin": "mm (default 1)",+    "net": "(optional) a net: its pads, tracks and vias",+    "rect": "(optional) [x1, y1, x2, y2] mm",+    "ref": "(optional) a designator, e.g. U1",+    "refs": "(optional) [designator]"+   },+   "longRunning": false,+   "name": "altium_zoom_to",+   "output": {+    "rect": "[left, bottom, right, top] mm framed"+   },+   "pitfalls": [+    "Needs the board current.",+    "Coordinates are Altium's (y up), as routing_state reports them."+   ],+   "related": [+    "altium_routing_state",+    "altium_run_command",+    "altium_animate_view"+   ],+   "statusVerb": null,+   "summary": "Frame the PCB view on a rectangle, a part (or several) or a whole net, with a margin.",+   "timeoutSeconds": 60+  },+  {+   "example": {+    "designator": "U1"+   },+   "hint": "Use it to verify an imported footprint on a altium_test_board: pad layerId 74 = multi-layer (plated through), which is what altium_plate_vias produces.",+   "input": {+    "designator": "string (required) - a component on the current board, e.g. U1"+   },+   "longRunning": false,+   "name": "altium_footprint_primitives",+   "output": {+    "data": "{designator, pattern, count, otherPrimitives, primitives: [{kind: track|arc|pad|text, layerId, ...}]}",+    "roundtripMs": "int"+   },+   "pitfalls": [+    "Needs a .PcbDoc current: it reads a placed component, not a PcbLib member (altium_pcb_report reads libraries).",+    "3D bodies, fills and regions only show up in otherPrimitives."+   ],+   "related": [+    "altium_component_pins",+    "altium_plate_vias",+    "altium_test_board",+    "altium_pcb_report"+   ],+   "statusVerb": null,+   "summary": "Geometry dump of one PLACED component: its tracks, arcs, pads (with solder and paste mask expansion in um) and texts in mils with layer ids; other primitive kinds are counted.",+   "timeoutSeconds": 120+  },+  {+   "example": {+    "designator": "U1"+   },+   "hint": "Pin-level view of a sheet. params[] is empty unless you pass params:true; altium_read_schematic is the fuller parameter view.",+   "input": {+    "designator": "string - optional filter",+    "filePath": "path to the .SchDoc - optional, made current first (alias sheet)",+    "limit": "int, default 500",+    "paramNamesOnly": "bool, default false - parameter names without values (a library with blob parameters reads fast)",+    "params": "bool, default false - include each component's parameters",+    "pins": "bool, default true"+   },+   "longRunning": false,+   "name": "altium_schematic_data",+   "output": {+    "data": "{document, count, components: [{designator, partId, libReference, comment, description (empty for a workspace-managed part), kind (ComponentKind: 0 Standard, 1 Mechanical, 2 Graphical, 3 Net Tie BOM, 4 Net Tie, 5 Standard no BOM, 6 Jumper), itemGuid, vaultGuid, sourceLibrary, xMil, yMil, orientation, pins: [{number, name, electrical (0 Input, 1 IO, 2 Output, 3 OpenCollector, 4 Passive, 5 HiZ, 6 OpenEmitter, 7 Power), xMil, yMil, orientation, lengthMil}], params: []}]}",+    "roundtripMs": "int"+   },+   "pitfalls": [+    "One sheet per call. Without filePath the current document must be a .SchDoc: a schematic library is refused (no_current_schematic), not read as an empty sheet.",+    "electrical is Altium's pin-type ordinal, not a name."+   ],+   "related": [+    "altium_read_schematic",+    "altium_cross_check",+    "altium_show_schematic"+   ],+   "statusVerb": null,+   "summary": "Components on a schematic sheet (filePath, else the CURRENT sheet) with their pins (number, name, electrical type), position and orientation. designator filters to one part.",+   "timeoutSeconds": 120+  },+  {+   "example": {+    "filePath": "C:/proj/board.PrjPcb"+   },+   "hint": "A project has no document view of its own: follow with altium_show_schematic / altium_show_2d_board {filePath} for something to look at, or altium_compile {projectPath}.",+   "input": {+    "filePath": "path to a .PrjPcb - default: the bundled Bluetooth Sentinel example",+    "timeout": "seconds, default 60"+   },+   "longRunning": false,+   "name": "altium_show_project",+   "output": {+    "dialogs": "[..]",+    "dialogsDetected": "int",+    "document": "string",+    "elapsedSec": "float",+    "path": "string",+    "shot": "{image: base64 png, width, height}",+    "steps": "[{open, result, ms}]",+    "surface": "project",+    "title": "string",+    "windowEtiquette": "background"+   },+   "pitfalls": [+    "ALWAYS pass filePath for the user's work: the default opens Altium's example project in their session.",+    "Measured live 2026-09-20: opening documents RAISES Altium over the user's work even though the reply says background. One call per proof; never loop show_* verbs.",+    "success:false with dialogs[] means a box came up during the open: read it before anything else."+   ],+   "related": [+    "altium_show_schematic",+    "altium_show_2d_board",+    "altium_compile",+    "altium_open_project"+   ],+   "statusVerb": null,+   "summary": "Open a project (.PrjPcb) through the agent and return a proof frame of Altium's main window. With no filePath it opens Altium's bundled example project (Bluetooth Sentinel).",+   "timeoutSeconds": 120+  },+  {+   "example": {+    "filePath": "C:/proj/tof.SchDoc"+   },+   "hint": "LOOK at shot.image before claiming anything. Then altium_read_schematic for the data, or altium_animate_view {lane:'sch'} for zoom moves.",+   "input": {+    "filePath": "path to a .SchDoc - default: the bundled example sheet",+    "timeout": "seconds, default 60"+   },+   "longRunning": false,+   "name": "altium_show_schematic",+   "output": {+    "dialogs": "[..]",+    "dialogsDetected": "int",+    "document": "string",+    "elapsedSec": "float",+    "path": "string",+    "shot": "{image: base64 png, width, height}",+    "steps": "[{open | process, ...}]",+    "surface": "schematic",+    "title": "string",+    "windowEtiquette": "background"+   },+   "pitfalls": [+    "ALWAYS pass filePath: the default opens Altium's example design in the user's session.",+    "Measured live 2026-09-20: it RAISES Altium over the user's work. One call per proof; never loop it.",+    "After re-generating a sheet at the same path, altium_close_document the stale one first or the frame shows the OLD document."+   ],+   "related": [+    "altium_read_schematic",+    "altium_place_on_sch",+    "altium_animate_view",+    "altium_show_2d_board"+   ],+   "statusVerb": null,+   "summary": "Open a .SchDoc, zoom to fit, and return a proof frame. With no filePath it opens the bundled Bluetooth Sentinel top sheet.",+   "timeoutSeconds": 120+  },+  {+   "example": {+    "filePath": "C:/proj/tof.PcbDoc"+   },+   "hint": "It also makes the board the CURRENT document, so altium_read_board / altium_board_summary work right after. altium_show_3d_board for the 3D frame.",+   "input": {+    "filePath": "path to a .PcbDoc - default: the bundled example board",+    "timeout": "seconds, default 120"+   },+   "longRunning": false,+   "name": "altium_show_2d_board",+   "output": {+    "dialogs": "[..]",+    "dialogsDetected": "int",+    "document": "string",+    "elapsedSec": "float",+    "path": "string",+    "shot": "{image: base64 png, width, height}",+    "steps": "[{open | process, ...}]",+    "surface": "2d_board",+    "title": "string",+    "windowEtiquette": "background"+   },+   "pitfalls": [+    "ALWAYS pass filePath: the default opens Altium's example board in the user's session.",+    "Measured live 2026-09-20: it RAISES Altium over the user's work. One call per proof; never loop it."+   ],+   "related": [+    "altium_show_3d_board",+    "altium_read_board",+    "altium_set_layer_visibility",+    "altium_animate_view"+   ],+   "statusVerb": null,+   "summary": "Open a .PcbDoc, switch to 2D, zoom to fit, and return a proof frame. With no filePath it opens the bundled Bluetooth Sentinel board.",+   "timeoutSeconds": 120+  },+  {+   "example": {+    "filePath": "C:/proj/tof.PcbDoc"+   },+   "hint": "The proof for altium_test_board {pcbPath}: check every part shows a body. Missing body = the footprint has none (altium_add_body). altium_animate_view {preset:'showcase3d'} for motion.",+   "input": {+    "filePath": "path to a .PcbDoc - default: the bundled example board",+    "timeout": "seconds, default 120"+   },+   "longRunning": false,+   "name": "altium_show_3d_board",+   "output": {+    "dialogs": "[..]",+    "dialogsDetected": "int",+    "document": "string",+    "elapsedSec": "float",+    "path": "string",+    "shot": "{image: base64 png, width, height}",+    "steps": "[{open | process, ...}]",+    "surface": "3d_board",+    "title": "string",+    "windowEtiquette": "background"+   },+   "pitfalls": [+    "ALWAYS pass filePath: the default opens Altium's example board in the user's session.",+    "Measured live 2026-09-20: it RAISES Altium over the user's work. One call per proof; never loop it.",+    "It leaves the board in 3D mode for the user; altium_show_2d_board switches back."+   ],+   "related": [+    "altium_show_2d_board",+    "altium_test_board",+    "altium_add_body",+    "altium_animate_view"+   ],+   "statusVerb": null,+   "summary": "Open a .PcbDoc, switch to 3D, zoom to fit, and return a proof frame. With no filePath it opens the bundled Bluetooth Sentinel board.",+   "timeoutSeconds": 120+  },+  {+   "example": {+    "libraryPath": "C:/lib/Adom_ToF.SchLib"+   },+   "hint": "Then altium_show_symbol / altium_show_footprint / altium_show_3d_chip {libraryPath, name} for one member. altium_list_library gives the names without a frame.",+   "input": {+    "libraryPath": "path to a .SchLib/.PcbLib (alias: filePath) - default: the bundled SpiritLevel SchLib",+    "timeout": "seconds, default 60"+   },+   "longRunning": false,+   "name": "altium_show_library",+   "output": {+    "componentCount": "int",+    "components": "[string] up to 200",+    "dialogs": "[..]",+    "path": "string",+    "selected": "string",+    "shot": "{image: base64 png, width, height}",+    "steps": "[..]",+    "surface": "library",+    "windowEtiquette": "background"+   },+   "pitfalls": [+    "ALWAYS pass libraryPath: the default opens Altium's example library in the user's session.",+    "Measured live 2026-09-20: it RAISES Altium over the user's work. One call per proof; never loop it.",+    "errorCode proof_unavailable = opened and selected correctly, only the capture failed (window minimized)."+   ],+   "related": [+    "altium_list_library",+    "altium_show_symbol",+    "altium_show_footprint",+    "altium_show_3d_chip"+   ],+   "statusVerb": null,+   "summary": "Open a .SchLib or .PcbLib (kind taken from the extension), select its first component, return a proof frame and the component names.",+   "timeoutSeconds": 120+  },+  {+   "example": {+    "footprintName": "VL53L5CX",+    "libraryPath": "C:/lib/Adom_ToF.PcbLib"+   },+   "hint": "Check pads, silkscreen and courtyard in shot.image; altium_pcb_report {path, name} gives the numbers. altium_show_3d_chip for the body.",+   "input": {+    "footprintName": "string (alias: name) - default: the first footprint of YOUR library, SO-G3 in the demo library",+    "libraryPath": "path to a .PcbLib (alias: filePath) - default: the bundled SpiritLevel PcbLib",+    "timeout": "seconds, default 60"+   },+   "longRunning": false,+   "name": "altium_show_footprint",+   "output": {+    "available": "[string] - when the name was not found",+    "dialogs": "[..]",+    "path": "string",+    "selected": "string",+    "shot": "{image: base64 png, width, height}",+    "steps": "[..]",+    "surface": "footprint",+    "windowEtiquette": "background"+   },+   "pitfalls": [+    "Measured live 2026-09-20: it RAISES Altium over the user's work. One call per proof; never loop it.",+    "A wrong footprintName fails with available[] (up to 50 names): pick from it.",+    "An empty-looking canvas usually means a 3D body sits 50000 mil from the pads: altium_normalize_bodies."+   ],+   "related": [+    "altium_show_3d_chip",+    "altium_pcb_report",+    "altium_build_footprint",+    "altium_list_library"+   ],+   "statusVerb": null,+   "summary": "Open a PcbLib member in the footprint editor in 2D, zoom to fit, and capture a proof frame.",+   "timeoutSeconds": 120+  },+  {+   "example": {+    "footprintName": "VL53L5CX",+    "libraryPath": "C:/lib/Adom_ToF.PcbLib"+   },+   "hint": "The proof after altium_add_body / altium_import_part {step}. Pads with no body in the frame: altium_pcb_report to see whether a body exists, altium_normalize_bodies if it sits off the pads.",+   "input": {+    "footprintName": "string (alias: name) - default: the first footprint of YOUR library, SO-G3 in the demo library",+    "libraryPath": "path to a .PcbLib (alias: filePath) - default: the bundled SpiritLevel PcbLib",+    "timeout": "seconds, default 60"+   },+   "longRunning": false,+   "name": "altium_show_3d_chip",+   "output": {+    "available": "[string] - when the name was not found",+    "dialogs": "[..]",+    "path": "string",+    "selected": "string",+    "shot": "{image: base64 png, width, height}",+    "steps": "[..]",+    "surface": "3d_chip",+    "windowEtiquette": "background"+   },+   "pitfalls": [+    "Measured live 2026-09-20: it RAISES Altium over the user's work. One call per proof; never loop it.",+    "It leaves the library editor in 3D mode; altium_show_footprint switches back to 2D."+   ],+   "related": [+    "altium_show_footprint",+    "altium_add_body",+    "altium_normalize_bodies",+    "altium_pcb_report"+   ],+   "statusVerb": null,+   "summary": "Open a PcbLib member in the footprint editor in 3D, zoom to fit, and capture a proof frame of the part's 3D body.",+   "timeoutSeconds": 120+  },+  {+   "example": {+    "libraryPath": "C:/lib/Adom_ToF.PcbLib"+   },+   "hint": "Use the exact names with altium_show_symbol / altium_read_symbol / altium_show_footprint / altium_pcb_report.",+   "input": {+    "libraryPath": "path to a .SchLib/.PcbLib (alias: filePath) - default: the bundled SpiritLevel SchLib",+    "limit": "int, default 500"+   },+   "longRunning": false,+   "name": "altium_list_library",+   "output": {+    "data": "{kind: sch|pcb, count, names: [string]}",+    "kind": "sch | pcb",+    "path": "string",+    "roundtripMs": "int"+   },+   "pitfalls": [+    "It makes the library the current document.",+    "count is the true total; names[] stops at limit."+   ],+   "related": [+    "altium_show_library",+    "altium_read_symbol",+    "altium_pcb_report"+   ],+   "statusVerb": null,+   "summary": "Names of every component in a .SchLib or every footprint in a .PcbLib (kind taken from the extension). Opens the library; no screenshot.",+   "timeoutSeconds": 60+  },+  {+   "example": {+    "filePath": "C:/lib/Adom_ToF.SchLib"+   },+   "hint": "Pass filePath every time: every library authoring verb leaves the library UNSAVED. Then altium_check_dialogs - a 'Save As' box means the document never had a path.",+   "input": {+    "filePath": "path - optional; opened and made current before the save"+   },+   "longRunning": false,+   "name": "altium_save",+   "output": {+    "data": "{process, params}",+    "roundtripMs": "int"+   },+   "pitfalls": [+    "Without filePath it saves whatever has focus, which may be the user's document rather than yours.",+    "success only means the save process was dispatched; a Save As dialog blocks the engine until answered."+   ],+   "related": [+    "altium_close_document",+    "altium_check_dialogs",+    "altium_close"+   ],+   "statusVerb": null,+   "summary": "Save the focused document, or open filePath first and save that (WorkspaceManager:SaveObject on the focused document).",+   "timeoutSeconds": 60+  },+  {+   "example": {+    "hMm": 14,+    "pcbPath": "C:/proj/tof.PcbDoc",+    "wMm": 39+   },+   "hint": "Then altium_save {filePath: pcbPath} and altium_show_2d_board {filePath} to see it. altium_pcb_report {want:'board'} reads outlineMil back.",+   "input": {+    "hMm": "number, default 15",+    "mechOutline": "bool - also draw the rectangle as 0.1 mm tracks on Mechanical 1 so the fab Gerber (.GM1) has an outline",+    "pcbPath": "path to a .PcbDoc - optional; opened first",+    "timeout": "seconds, default 120",+    "wMm": "number, default 15",+    "x0Mm": "number, default 0",+    "y0Mm": "number, default 0"+   },+   "longRunning": false,+   "name": "altium_board_shape",+   "output": {+    "data": "{outlineMil: [left, bottom, right, top] read from the outline vertices, outlineSource, mechOutlineTracks}",+    "roundtripMs": "int"+   },+   "pitfalls": [+    "errorCode no_board_outline: the current document is a PcbLib (its internal board has no outline) - pass pcbPath.",+    "bad_size = wMm/hMm not greater than 0; bad_number = a value was not numeric.",+    "It REPLACES the outline; the previous shape is not kept.",+    "The board shape alone is NOT plotted into any Gerber: pass mechOutline:true (once, at the final size: each call adds a new set of tracks)."+   ],+   "related": [+    "altium_test_board",+    "altium_pcb_report",+    "altium_show_2d_board",+    "altium_save"+   ],+   "statusVerb": null,+   "summary": "Redefine the CURRENT board's outline as a wMm x hMm rectangle at (x0Mm, y0Mm) by rewriting the board outline's segments.",+   "timeoutSeconds": 120+  },+  {+   "example": {+    "name": "VL53L5CX",+    "path": "C:/lib/Adom_ToF.PcbLib"+   },+   "hint": "bodies[] rows follow bodyCols (layerId,wMil,hMil,leftMil,bottomMil,standoffMil,overallMil,hasModel). A body whose leftMil is ~50000 away from padsBboxMil needs altium_normalize_bodies.",+   "input": {+    "name": "string - footprint name filter (library form)",+    "path": "path to a .PcbLib or .PcbDoc - optional; opened first",+    "timeout": "seconds, default 60",+    "want": "'board' - report the focused BOARD even though a PcbLib is also answering"+   },+   "longRunning": false,+   "name": "altium_pcb_report",+   "output": {+    "data": "{kind: library, components: [{name, pads, padsBboxMil, census, bodies}], bodyCols} | {kind: board, outlineMil, components: [{.., pads, padsBboxMil, nets, bodies}], census, tracks, bodyCols}",+    "roundtripMs": "int"+   },+   "pitfalls": [+    "Altium's GetCurrentPCBLibrary answers even when a BOARD is focused: pass want:'board' for a .PcbDoc or you get the library report.",+    "A footprint with bodies:[] places with no 3D model."+   ],+   "related": [+    "altium_footprint_primitives",+    "altium_normalize_bodies",+    "altium_add_body",+    "altium_show_3d_chip"+   ],+   "statusVerb": null,+   "summary": "Numbers instead of pixels: for a PcbLib, every footprint's pads, pad bounding box, primitive census and 3D bodies; for a board, outline, components with nets and bodies, census and tracks.",+   "timeoutSeconds": 60+  },+  {+   "example": {+    "projectPath": "C:/proj/board.PrjPcb"+   },+   "hint": "Zero messages = clean. Fix what messages[] names, recompile, then altium_run_drc for the board side.",+   "input": {+    "limit": "int, default 200 messages",+    "objectKind": "string, default 'Project'",+    "projectPath": "path to a .PrjPcb (alias: filePath) - default: the bundled Bluetooth Sentinel example",+    "timeout": "seconds"+   },+   "longRunning": false,+   "name": "altium_compile",+   "output": {+    "byClass": "{class: n}",+    "elapsedSec": "float",+    "messageCount": "int",+    "messages": "[{class, source, document, text}]",+    "project": "path",+    "windowEtiquette": "background"+   },+   "pitfalls": [+    "ALWAYS pass projectPath: the default compiles Altium's example project in the user's session.",+    "messages[] is the WHOLE Messages panel after the compile, capped at limit; messageCount is the true total."+   ],+   "related": [+    "altium_run_erc",+    "altium_run_drc",+    "altium_open_project",+    "altium_cross_check"+   ],+   "statusVerb": null,+   "summary": "Compile a project and return Altium's Messages panel (ERC + connectivity) with counts by class. With no path it compiles Altium's bundled example project.",+   "timeoutSeconds": 180+  },+  {+   "example": {+    "projectPath": "C:/proj/board.PrjPcb"+   },+   "hint": "byClass gives the error/warning split; read messages[].text and .document for each. Then altium_run_drc for the layout.",+   "input": {+    "limit": "int, default 200 messages",+    "objectKind": "string, default 'Project'",+    "projectPath": "path to a .PrjPcb (alias: filePath) - default: the bundled Bluetooth Sentinel example",+    "timeout": "seconds"+   },+   "longRunning": false,+   "name": "altium_run_erc",+   "output": {+    "byClass": "{class: n}",+    "elapsedSec": "float",+    "messageCount": "int",+    "messages": "[{class, source, document, text}]",+    "project": "path",+    "windowEtiquette": "background"+   },+   "pitfalls": [+    "ALWAYS pass projectPath: the default compiles Altium's example project.",+    "Needs the PROJECT path, not a sheet: a free .SchDoc has no project to compile."+   ],+   "related": [+    "altium_compile",+    "altium_run_drc",+    "altium_review_board"+   ],+   "statusVerb": null,+   "summary": "Electrical rules check. Same handler as altium_compile: in Altium, compiling the project IS the ERC.",+   "timeoutSeconds": 180+  },+  {+   "example": {+    "filePath": "C:/proj/board.PcbDoc"+   },+   "hint": "violationCount is the true total; violations[] stops at limit. Altium also opens its HTML DRC report as a new document: altium_state shows it, altium_close_document {path} removes it.",+   "input": {+    "filePath": "path to a .PcbDoc - default: the bundled Bluetooth Sentinel board",+    "limit": "int, default 200 violations",+    "timeout": "seconds to wait for the DRC, default 180"+   },+   "longRunning": false,+   "name": "altium_run_drc",+   "output": {+    "board": "path",+    "dialogs": "[..]",+    "elapsedSec": "float",+    "steps": "[..]",+    "violationCount": "int",+    "violations": "[{rule, description}]",+    "windowEtiquette": "background"+   },+   "pitfalls": [+    "ALWAYS pass filePath: the default runs DRC on Altium's example board.",+    "drc_dialog_not_found = the DRC dialog never opened; check that a PCB is focused (altium_window_info).",+    "Never run PCB:DesignRuleCheck through altium_run_command: the modal freezes the agent with nobody to click it."+   ],+   "related": [+    "altium_read_rules",+    "altium_rules_by_kind",+    "altium_review_board",+    "altium_compile"+   ],+   "statusVerb": null,+   "summary": "Run the design rule check on a board and read the violation markers back. PCB:DesignRuleCheck opens a MODAL; the bridge clicks 'Run Design Rule Check' through UIA in the background, waits for the agent, re-activates the board and reads the violations.",+   "timeoutSeconds": 300+  },+  {+   "example": {+    "medium": "Folder Structure",+    "outjobPath": "C:/proj/Fabrication.OutJob",+    "projectPath": "C:/proj/board.PrjPcb"+   },+   "hint": "files[] is the proof. Pull what you need with ab's pull_file. OutJobs OPEN what they generate (CAMtastic, PDFs): altium_state afterwards and altium_close_document the viewers.",+   "input": {+    "medium": "string, default 'Folder Structure' - the OutputMedium name inside the OutJob",+    "objectKind": "string, default 'OutputBatch'",+    "outjobPath": "path to an .OutJob (alias: filePath) - default: the bundled example Fabrication.OutJob",+    "projectPath": "path to the .PrjPcb the OutJob belongs to; opened first",+    "timeout": "seconds, default 300"+   },+   "longRunning": false,+   "name": "altium_run_outjob",+   "output": {+    "agent": "{outjob, medium, objectKind}",+    "dialogs": "[..]",+    "elapsedSec": "float",+    "files": "[relative paths] up to 60",+    "medium": "string",+    "outjob": "path",+    "outputDir": "path | null",+    "windowEtiquette": "background"+   },+   "pitfalls": [+    "Pass projectPath with your own OutJob: outputDir/files are looked up beside the PROJECT, and without it beside the OutJob.",+    "medium must be the name of an output medium inside the OutJob. The agent cannot tell whether it ran: files[] is the only proof.",+    "A dialog in dialogs[] means a medium needed a decision (printer, PDF path): read dialogs[].shot."+   ],+   "related": [+    "altium_export_gerber",+    "altium_export_bom",+    "altium_export_step",+    "altium_check_dialogs"+   ],+   "statusVerb": null,+   "summary": "Run one output medium of an .OutJob (gerbers, drill, BOM, PDFs...) and list what it wrote under 'Project Outputs for <project>'.",+   "timeoutSeconds": 600+  },+  {+   "example": {+    "outjobPath": "C:/proj/Fabrication.OutJob",+    "projectPath": "C:/proj/board.PrjPcb"+   },+   "hint": "Check files[] for the gerber and NC drill files, then pull them with ab's pull_file. Close the CAMtastic documents the OutJob opened (altium_state, altium_close_document).",+   "input": {+    "medium": "string, default 'Folder Structure'",+    "outjobPath": "path to an .OutJob - default: the bundled example Fabrication.OutJob",+    "projectPath": "path to the .PrjPcb",+    "timeout": "seconds, default 300"+   },+   "longRunning": false,+   "name": "altium_export_gerber",+   "output": {+    "dialogs": "[..]",+    "elapsedSec": "float",+    "files": "[relative paths]",+    "outjob": "path",+    "outputDir": "path | null",+    "surface": "gerber"+   },+   "pitfalls": [+    "There is NO board-path argument: without outjobPath it exports Altium's EXAMPLE design. The user's project needs its own OutJob.",+    "OutJobs open what they generate: expect CAM documents left open in the user's session."+   ],+   "related": [+    "altium_run_outjob",+    "altium_export_bom",+    "altium_export_step",+    "altium_run_drc"+   ],+   "statusVerb": null,+   "summary": "Gerber/drill export: altium_run_outjob with the fabrication OutJob and the 'Folder Structure' medium as defaults.",+   "timeoutSeconds": 300+  },+  {+   "example": {+    "boards": 10,+    "projectPath": "C:/proj/board.PrjPcb"+   },+   "hint": "Parameter names are matched across libraries (MPN = Manufacturer Part Number = Mfr Part #; Mouser Part # / Digi-Key Part Number; LCSC Part # / JLCPCB Part #). Missing numbers: look them up (adom-parts-search search <MPN>) and write them back with altium_sheet_params, then run this again.",+   "input": {+    "boards": "int, default 1 - multiplies the quantities in the Mouser/DigiKey files",+    "outputDir": "path - default <project folder>\\BOM",+    "pcbPath": "path - optional, default the project's board",+    "projectPath": "path to the .PrjPcb (required)",+    "timeout": "seconds per sheet read, default 120",+    "variant": "string - optional, a project variant's description (Not Fitted parts become DNP, alternates are listed)",+    "write": "bool, default true"+   },+   "longRunning": false,+   "name": "altium_bom",+   "output": {+    "alternates": "[{designator, alternatePart}]",+    "checks": "{missingMpn, missingManufacturer, missingPartNumbers{mouser, digikey, lcsc}, inconsistent[{field, designators, values}], dnp, excludedKinds, sheetNotOnBoard, boardNotOnSheet}",+    "files": "[path]",+    "fittedParts": "int",+    "lines": "[{quantity, designators, value, comment, description, footprint, package, mpn, manufacturer, mouser, digikey, lcsc, libReference}]",+    "variants": "[string]"+   },+   "pitfalls": [+    "Graphical, Net Tie (no BOM) and Standard (no BOM) components are left out (checks.excludedKinds).",+    "DNP: a variant's Not Fitted parts, or a DNP / Do Not Populate / Fitted=false parameter.",+    "Variants are read from the .PrjPcb text; alternate parts are listed, not substituted.",+    "CPL rotations are Altium's: JLCPCB's preview may need per-footprint corrections.",+    "A multi-channel (repeated) sheet is read once: its copies' designators land in checks.boardNotOnSheet.",+    "Only the parameters a BOM needs are read (some libraries carry huge blob parameters); a workspace-managed part's description comes from a Description parameter, never its vault."+   ],+   "related": [+    "altium_sheet_params",+    "altium_export_manufacturing",+    "altium_read_schematic"+   ],+   "statusVerb": null,+   "summary": "The project's bill of materials read straight from Altium: every schematic sheet the .PrjPcb lists (placed parts and their parameters) merged with the board (footprint, position, side), a variant applied, grouped one line per manufacturer part number, checked for what an order needs, and written as bom.csv, bom-jlcpcb.csv + cpl-jlcpcb.csv, bom-mouser.csv and bom-digikey.csv (+ bom.json).",+   "timeoutSeconds": 600+  },+  {+   "example": {+    "designator": "U2",+    "library": "C:/proj/Lib.SchLib",+    "sheet": "C:/proj/Main.SchDoc",+    "symbol": "TPS54202DDCR",+    "x": 3000,+    "y": 4000+   },+   "hint": "Then wire it: altium_sch_connect {sheet, connections:[{designator, pin, net}]}; number a '?' designator with altium_annotate; push it to the board with altium_update_pcb.",+   "input": {+    "comment": "string, default the library's",+    "designator": "string, default the library's (e.g. R?)",+    "footprint": "a PcbLib footprint name to link instead of the library's own",+    "library": "path to the .SchLib (required)",+    "partId": "int, default 1 (a multi-part component's part)",+    "pcbLib": "path of the .PcbLib the footprint lives in (optional)",+    "rotation": "0|90|180|270",+    "save": "bool, default true",+    "sheet": "path to the .SchDoc (required)",+    "symbol": "the component's name in that library (required)",+    "x": "mil, default 1000 (snapped to 100)",+    "y": "mil, default 1000"+   },+   "longRunning": false,+   "name": "altium_sch_place_part",+   "output": {+    "atMil": "[x, y]",+    "designator": "string",+    "footprints": "[string]",+    "libref": "string",+    "pins": "int",+    "saved": "bool"+   },+   "pitfalls": [+    "Places part 1 of a multi-part component unless partId says otherwise.",+    "Sheet coordinates are mils from the sheet's lower-left corner; altium_schematic_data shows where the other parts are."+   ],+   "related": [+    "altium_sch_connect",+    "altium_annotate",+    "altium_update_pcb",+    "altium_list_library"+   ],+   "statusVerb": null,+   "summary": "Place a component from a .SchLib onto an EXISTING sheet at a position, with its library parameters (MPN, Manufacturer, distributor numbers) and its footprint link.",+   "timeoutSeconds": 120+  },+  {+   "example": {+    "connections": [+     {+      "designator": "U2",+      "kind": "power",+      "net": "GND",+      "pin": "1"+     },+     {+      "designator": "U2",+      "net": "VIN",+      "pin": "VIN"+     }+    ],+    "sheet": "C:/proj/Main.SchDoc"+   },+   "hint": "Net labels with the same text connect across the project; power ports connect by net name everywhere. Check the result with altium_compile (ERC) or altium_schematic_data.",+   "input": {+    "connections": "[{designator, pin (number or name), net, kind: label|power|wire|noerc|port (default label), io?: input|output|bidirectional (port), stubMil (default 200)}] (required)",+    "save": "bool, default true",+    "sheet": "path to the .SchDoc (required)"+   },+   "longRunning": false,+   "name": "altium_sch_connect",+   "output": {+    "connected": "int",+    "failed": "[..]",+    "results": "[{designator, pin, net, kind, result, endMil}]",+    "saved": "bool"+   },+   "pitfalls": [+    "A pin that already has a wire gets a second stub; read the sheet first when unsure.",+    "A part rotated after connecting leaves its stubs behind."+   ],+   "related": [+    "altium_sch_place_part",+    "altium_compile",+    "altium_schematic_data"+   ],+   "statusVerb": null,+   "summary": "Connect pins of placed parts: a wire stub from each pin's outer end plus a net label (kind label), a power port (kind power: GND-like nets get a ground symbol) or the stub alone (kind wire); or mark a pin left open on purpose with a No ERC marker (kind noerc, no net). One undo step.",+   "timeoutSeconds": 90+  },+  {+   "example": {+    "edits": [+     {+      "action": "comment",+      "designator": "R5",+      "text": "10k"+     },+     {+      "action": "delete",+      "designator": "C9"+     }+    ],+    "sheet": "C:/proj/Main.SchDoc"+   },+   "hint": "Wires do not follow a moved part; reconnect with altium_sch_connect. Renames and deletions reach the board with altium_update_pcb.",+   "input": {+    "edits": "[{designator, action: move|moveBy|rotate|designator|comment|delete, x,y | dx,dy | degrees | to | text}] (required)",+    "save": "bool, default true",+    "sheet": "path to the .SchDoc (required)"+   },+   "longRunning": false,+   "name": "altium_sch_edit",+   "output": {+    "applied": "int",+    "failed": "[..]",+    "results": "[{designator, action, result}]",+    "saved": "bool"+   },+   "pitfalls": [+    "Every part of a multi-part component with that designator is changed together."+   ],+   "related": [+    "altium_sch_connect",+    "altium_update_pcb",+    "altium_sheet_params"+   ],+   "statusVerb": null,+   "summary": "Change placed parts on a sheet: move (to x,y mil), moveBy (dx,dy), rotate (degrees), rename (designator), re-comment (comment), delete. One undo step.",+   "timeoutSeconds": 90+  },+  {+   "example": {+    "designator": "C10",+    "library": "C:/proj/Lib.SchLib",+    "sheet": "C:/proj/Main.SchDoc",+    "symbol": "CC0402-100NF"+   },+   "hint": "Then altium_update_pcb carries the new part's footprint and parameters to the board. A workspace part: altium_workspace_place it beside, connect, then altium_sch_edit delete the old one.",+   "input": {+    "designator": "the part to replace (required)",+    "footprint": "optional footprint name",+    "keepComment": "bool, default false (the new part's own comment)",+    "library": "path to the .SchLib (required)",+    "pcbLib": "optional .PcbLib for that footprint",+    "save": "bool, default true",+    "sheet": "path to the .SchDoc (required)",+    "symbol": "the new part's name in it (required)"+   },+   "longRunning": false,+   "name": "altium_sch_swap_part",+   "output": {+    "newPinsNotOnOld": "[pin]",+    "now": "{libReference, footprints}",+    "oldPinsNotOnNew": "[pin]",+    "pinsJoined": "[{pin, from, to}]"+   },+   "pitfalls": [+    "Pins are matched by number.",+    "Multi-part components are refused."+   ],+   "related": [+    "altium_sch_place_part",+    "altium_sch_edit",+    "altium_sch_connect",+    "altium_update_pcb"+   ],+   "statusVerb": null,+   "summary": "Replace a placed part with another library part (a different value, package or symbol) at the same place, designator and rotation, keeping its connections: where a new pin ends elsewhere, a wire joins it to where the old pin ended.",+   "timeoutSeconds": 120+  },+  {+   "example": {+    "designators": [+     "U1",+     "L1"+    ],+    "sheet": "C:/proj/Main.SchDoc"+   },+   "hint": "To find which sheet holds a part, altium_bom (or altium_schematic_data per sheet) lists every designator with its sheet.",+   "input": {+    "designators": "[string] or 'R1,U2' (required)",+    "sheet": "path to the .SchDoc (required)",+    "shot": "bool, default true"+   },+   "longRunning": false,+   "name": "altium_sch_zoom",+   "output": {+    "notOnSheet": "[string]",+    "parts": "[{designator, xMil, yMil}]",+    "shot": "frame of Altium's main window"+   },+   "pitfalls": [],+   "related": [+    "altium_zoom_to",+    "altium_schematic_data",+    "altium_sch_edit"+   ],+   "statusVerb": null,+   "summary": "Find parts on a sheet: select them (everything else deselected), frame the view on them and return a proof frame. The schematic half of cross-probing (the board half: altium_zoom_to).",+   "timeoutSeconds": 60+  },+  {+   "example": {+    "name": "Lite",+    "notFitted": [+     "R5",+     "C9",+     "J2"+    ],+    "projectPath": "C:/proj/board.PrjPcb"+   },+   "hint": "altium_bom {variant} then gives that build's BOM (not-fitted parts dropped). Saves the project's documents, closes and reopens the project to load the change.",+   "input": {+    "fitted": "[designators] to put back (their variation is dropped)",+    "name": "variant name; omit to list",+    "notFitted": "[designators] to mark Not Fitted (alias dnp)",+    "projectPath": "path to the .PrjPcb (required)",+    "remove": "bool - delete the variant"+   },+   "longRunning": false,+   "name": "altium_variants",+   "output": {+    "altium": "{variants:[{name, variations:[{designator, kind}]}]} as Altium reads them (kind 1 = not fitted, 2 = alternate)",+    "variants": "[{name, notFitted, alternates}]"+   },+   "pitfalls": [+    "Variants with parameter variations are refused (edit those in Altium's Variant Management).",+    "Parts are named by their sheet's unique id: on a multi-channel (repeated) sheet every channel gets the same variation.",+    "Alternate parts are listed but not created here."+   ],+   "related": [+    "altium_bom",+    "altium_export_manufacturing",+    "altium_run_outjob"+   ],+   "statusVerb": null,+   "summary": "Assembly variants of a project (which parts are fitted on which build): list them, make or change one with not-fitted parts (or put parts back), or remove one. Altium's script API cannot create variants, so the bridge writes them into the .PrjPcb, reloads the project and reads back what Altium holds.",+   "timeoutSeconds": 300+  },+  {+   "example": {+    "mech": 1,+    "widthMm": 0.1+   },+   "hint": "altium_export_manufacturing checks the outline Gerbers and calls this itself when they are empty (addOutline:false stops it). altium_save keeps the outline.",+   "input": {+    "filePath": "path to the .PcbDoc - optional",+    "mech": "mechanical layer number, default 1",+    "replace": "bool - draw even when the layer already holds primitives",+    "widthMm": "line width, default 0.1"+   },+   "longRunning": false,+   "name": "altium_board_outline",+   "output": {+    "alreadyThere": "primitives the layer held",+    "drawn": "segments drawn",+    "layer": "string"+   },+   "pitfalls": [+    "A layer that already holds anything is left alone unless replace:true (it may be the outline already, drawn by hand)."+   ],+   "related": [+    "altium_export_manufacturing",+    "altium_set_outline",+    "altium_board_shape"+   ],+   "statusVerb": null,+   "summary": "Draw the board shape as tracks and arcs on a mechanical layer: the outline a fab house reads from the Gerbers. A board built by script has its shape but nothing on an outline layer, so its GM1/GKO Gerbers are empty.",+   "timeoutSeconds": 60+  },+  {+   "example": {+    "netClass": "Power",+    "widthMm": 0.5+   },+   "hint": "Run altium_routing_drc {full:true} after: wider copper can break clearance to neighbours.",+   "input": {+    "filePath": "path to the .PcbDoc - optional",+    "layer": "top | bottom | mid N - optional, default every layer",+    "netClass": "class name (its nets)",+    "nets": "[string] board net names",+    "widthMm": "number (required)"+   },+   "longRunning": false,+   "name": "altium_set_track_width",+   "output": {+    "byNet": "{NET: count}",+    "changed": "int",+    "drcAfter": "{errors, unconnected}"+   },+   "pitfalls": [+    "Copper inside footprints (pads' own tracks) is left alone."+   ],+   "related": [+    "altium_net_classes",+    "altium_route_net",+    "altium_routing_drc",+    "altium_add_rule"+   ],+   "statusVerb": null,+   "summary": "Set the width of the EXISTING tracks and arcs on nets or a net class (rules only steer new routing). Widen power nets after routing, or thin a net that fails clearance.",+   "timeoutSeconds": 120+  },+  {+   "example": {+    "query": "100nF 0402"+   },+   "hint": "Then altium_workspace_place {sheet, query, pick} puts one on a sheet. Altium's 365 API is a closed beta; the signed-in Altium reaches the workspace itself (altium_signin_status).",+   "input": {+    "limit": "int, default 25",+    "query": "text: an MPN, a value and package ('100nF 0402'), a part name (required)"+   },+   "longRunning": false,+   "name": "altium_workspace_search",+   "output": {+    "count": "int",+    "results": "[{index, name, description, footprint}]"+   },+   "pitfalls": [+    "Takes 5-40 s (Altium's WPF panel answers UI Automation slowly; slower when the panel must first be opened).",+    "It types into the panel's search box: Altium comes to the front."+   ],+   "related": [+    "altium_workspace_place",+    "altium_signin_status",+    "altium_bom"+   ],+   "statusVerb": null,+   "summary": "Search the Altium 365 workspace's components (the shared cloud library Altium is signed in to) through Altium's own Components panel: name, description and footprint per result.",+   "timeoutSeconds": 180+  },+  {+   "example": {+    "query": "100nF 0402 50V",+    "sheet": "C:/proj/Main.SchDoc",+    "x": 3000,+    "y": 2000+   },+   "hint": "Then altium_annotate (for a '?'), altium_sch_connect, altium_update_pcb. A part from a local .SchLib goes on with altium_sch_place_part instead.",+   "input": {+    "designator": "optional, else Altium's ('C?')",+    "pick": "result index (0-based) or text its name contains; default the first",+    "query": "the search (required; an MPN finds one part)",+    "save": "bool, default true",+    "sheet": "path to the .SchDoc (required)",+    "x": "mils",+    "y": "mils"+   },+   "longRunning": false,+   "name": "altium_workspace_place",+   "output": {+    "picked": "{name, description, footprint}",+    "placed": "{designator, libReference, comment, itemGuid, sourceLibrary, xMil, yMil}"+   },+   "pitfalls": [+    "Takes 15-70 s. Uses real clicks (double-click the result, click the sheet): Altium comes to the front.",+    "The part lands near the view's lower-left corner first, then moves to x,y: wires do not follow, so connect it after."+   ],+   "related": [+    "altium_workspace_search",+    "altium_sch_place_part",+    "altium_annotate",+    "altium_sch_connect",+    "altium_update_pcb"+   ],+   "statusVerb": null,+   "summary": "Put a part from the Altium 365 workspace on a sheet: search, pick a result, place it (Altium's own placement, so the part keeps its workspace link, parameters and footprint), move it to x,y and name it.",+   "timeoutSeconds": 300+  },+  {+   "example": {+    "pcbPath": "C:/proj/Board.PcbDoc",+    "projectPath": "C:/proj/Board.PrjPcb"+   },+   "hint": "A pin with no pad is a net that can never reach copper: change the footprint (altium_swap_footprint) or renumber the symbol's pins before routing.",+   "input": {+    "pcbPath": "its .PcbDoc",+    "projectPath": "the .PrjPcb"+   },+   "longRunning": false,+   "name": "altium_pin_pad_check",+   "output": {+    "matching": "int",+    "mismatches": "[{ref, pinsWithoutPad, padsWithoutPin, pins, pads}]",+    "ok": "bool - every pin has a pad",+    "onBoardOnly": "[ref]",+    "onSheetsOnly": "[ref]"+   },+   "pitfalls": [+    "Mounting and thermal pads (MH*, MP*, SH*, EP*, PAD*, TAB*, unnamed or 0) are expected to have no pin."+   ],+   "related": [+    "altium_schematic_review",+    "altium_update_pcb",+    "altium_swap_footprint",+    "altium_component_pins"+   ],+   "statusVerb": null,+   "summary": "Do the footprints fit their symbols? Part by part, the symbol's pin numbers (from the sheet files) against the footprint's pad names (from the board): pins with no pad, pads with no pin, and parts on only the sheets or only the board.",+   "timeoutSeconds": 200+  },+  {+   "example": {+    "matchTo": "DP",+    "net": "DN"+   },+   "hint": "Then altium_net_lengths (the pair or group), altium_routing_drc. For a matched group, tune each short net to the longest (matchTo).",+   "input": {+    "clearance": "to other copper, default 0.2",+    "dryRun": "bool - plan only",+    "expectedRevision": "optional",+    "filePath": "the .PcbDoc (opened first; else the current board)",+    "matchTo": "or: a net whose length to match",+    "maxAmplitudeMm": "bump height limit, default 3",+    "net": "the net to lengthen",+    "pitchMm": "one bump's length along the track (default 4 x width, at least 4 x clearance + 2 x width)",+    "targetMm": "the length it should have"+   },+   "longRunning": false,+   "name": "altium_length_tune",+   "output": {+    "changed": "bool",+    "errorMm": "number",+    "lengthBeforeMm": "number",+    "lengthMm": "number",+    "plan": "{layer, replaces, bumps, amplitudeMm, pitchMm, side, addsMm, points}",+    "targetMm": "number"+   },+   "pitfalls": [+    "Only lengthens: a net already longer than its target is left alone.",+    "One serpentine per call, in one track; call again to add more elsewhere."+   ],+   "related": [+    "altium_net_lengths",+    "altium_route_pair",+    "altium_add_rule",+    "altium_routing_drc"+   ],+   "statusVerb": null,+   "summary": "Lengthen a routed net to a target length, or to match another net (a pair's skew, a DDR byte lane): a serpentine in its longest straight track that has room beside it, the bumps sized to add exactly the length missing, every bump clear of other copper and inside the board.",+   "timeoutSeconds": 120+  },+  {+   "example": {+    "apply": true,+    "powerWidthMm": 0.6+   },+   "hint": "Then scope rules to them (InNetClass('Power')), size the pairs with altium_impedance {apply}, and check power with altium_current_capacity.",+   "input": {+    "apply": "bool - write the classes and pairs (default: propose only)",+    "filePath": "the .PcbDoc (opened first; else the current board)",+    "powerWidthMm": "the Power class's width rule, default 0.5"+   },+   "longRunning": false,+   "name": "altium_auto_classes",+   "output": {+    "applied": "bool",+    "proposal": "{classes:{GND, Power, Clock, HighSpeed}, pairs:[{name, pos, neg}], unclassified}",+    "written": "[{class|pair, success}]"+   },+   "pitfalls": [+    "Names only: a net called N1 stays unclassified. Read the proposal before apply."+   ],+   "related": [+    "altium_net_classes",+    "altium_diff_pairs",+    "altium_impedance",+    "altium_add_rule",+    "altium_current_capacity"+   ],+   "statusVerb": null,+   "summary": "Net classes from the nets' own names: GND, Power (VCC, VDD, 3V3, VBAT...), Clock, HighSpeed (USB, Ethernet, HDMI, PCIe, DDR, MIPI, LVDS, SATA...) and the differential pairs their _P/_N, +/-, DP/DN names spell; a proposal first, written as Altium net classes (Power with a width rule) and pairs with apply.",+   "timeoutSeconds": 180+  },+  {+   "example": {+    "marginMm": 0.25+   },+   "hint": "Move what it names (altium_move_parts, altium_find_space for a clear spot), then route.",+   "input": {+    "edgeMm": "pad to board edge, default 0.5",+    "filePath": "the .PcbDoc (opened first; else the current board)",+    "marginMm": "courtyard margin round each part, default 0.1 (parts closer than 0.2 mm are flagged)"+   },+   "longRunning": false,+   "name": "altium_placement_check",+   "output": {+    "courtyardOverlaps": "[{parts, side, overlapMm}]",+    "nearEdge": "[{ref, padToEdgeMm}]",+    "offOrOverEdge": "[{ref, where}]",+    "ok": "bool",+    "onMountingHoles": "[{ref, hole}]",+    "parts": "int"+   },+   "pitfalls": [+    "A part's box is its pads and its 3D body, so an L-shaped part reads as its bounding box; on or off the board is judged by its pads."+   ],+   "related": [+    "altium_move_parts",+    "altium_find_space",+    "altium_height_check",+    "altium_board_features"+   ],+   "statusVerb": null,+   "summary": "Review the placement before routing: parts whose courtyards (pads and body, grown by a margin) overlap on the same side, parts off the board or over its edge, pads too close to the edge for assembly and depanelling, and parts on a mounting hole.",+   "timeoutSeconds": 180+  },+  {+   "example": {+    "pcbPath": "C:/proj/Board.PcbDoc",+    "projectPath": "C:/proj/Board.PrjPcb"+   },+   "hint": "Open the file in a browser or send it; each section names the verb to run for its full detail.",+   "input": {+    "out": "the .html to write (default '<board> report.html' beside the project)",+    "pcbPath": "its .PcbDoc",+    "projectPath": "the .PrjPcb"+   },+   "longRunning": false,+   "name": "altium_board_report",+   "output": {+    "elapsedSec": "number",+    "needsWork": "int",+    "pass": "int",+    "picture": "bool",+    "report": "path",+    "sections": "[{title, ok (true|false|null = info), summary}]"+   },+   "pitfalls": [+    "Runs Altium's full batch DRC and frames the board in Altium's window for the picture (no focus taken).",+    "Takes about a minute on a small board."+   ],+   "related": [+    "altium_fab_check",+    "altium_assembly_check",+    "altium_schematic_review",+    "altium_pin_pad_check",+    "altium_return_path",+    "altium_copper_balance",+    "altium_height_check",+    "altium_routing_drc"+   ],+   "statusVerb": null,+   "summary": "One shareable page per board: every check run against the live board and its project (Altium's DRC, JLCPCB fab limits and assembly tiers, the schematic review, symbols against footprints, impedance widths, return paths, copper balance, 3D heights) with a picture of the board, written as a single HTML file.",+   "timeoutSeconds": 900+  },+  {+   "example": {+    "pitchMm": 1.1,+    "ref": "U3"+   },+   "hint": "Pour the pad's net on the far side (altium_apply_pours) so the heat has somewhere to go, ask the fab to tent or plug vias in pads, then altium_routing_drc. adom-fields solves the heat itself.",+   "input": {+    "drill": "mm (default the board's via rule)",+    "dryRun": "bool - plan only",+    "expectedRevision": "optional",+    "filePath": "the .PcbDoc (opened first; else the current board)",+    "insetMm": "from the pad's edge, default 0.1",+    "pad": "the pad's name (default the part's largest pad)",+    "pitchMm": "default via size + 0.5, at least 1.0",+    "ref": "the part (e.g. U3)",+    "size": "via diameter mm (default the board's via rule)"+   },+   "longRunning": false,+   "name": "altium_thermal_vias",+   "output": {+    "applied": "int",+    "failed": "int",+    "planned": "{part, pad, net, padMm, vias, pitchMm, via, points}"+   },+   "pitfalls": [+    "Open vias in a pad wick solder away from it during reflow: tent or plug them, or keep drills at 0.3 mm or smaller."+   ],+   "related": [+    "altium_apply_pours",+    "altium_board_features",+    "altium_routing_drc",+    "altium_current_capacity"+   ],+   "statusVerb": null,+   "summary": "A thermal via grid in a part's exposed pad (the ground or thermal pad under a QFN, DFN, PowerPAD or regulator): centred, inset from the pad's edge, sized by the board's Routing Via rule, on the pad's net.",+   "timeoutSeconds": 120+  },+  {+   "example": {+    "diffOhms": 90,+    "from": {+     "neg": "U1.15",+     "pos": "U1.14"+    },+    "neg": "USB_DN",+    "pos": "USB_DP",+    "to": {+     "neg": "J1.2",+     "pos": "J1.3"+    },+    "via": [+     [+      20,+      10+     ],+     [+      30,+      10+     ]+    ]+   },+   "hint": "Then altium_net_lengths for the routed skew, altium_add_rule {kind:\"diffpair\"} to hold width and gap, and altium_routing_drc.",+   "input": {+    "clearance": "to other nets' pads when checking the plan, default 0.15",+    "diffOhms": "solve width (and gap = width unless given) for this differential impedance on the layer",+    "fanMm": "fan-out length at each end, default 1",+    "force": "bool - route even when the plan check finds problems",+    "from": "{pos: \"U1.3\", neg: \"U1.4\"} the source pads",+    "gap": "mm",+    "layer": "F.Cu (default) | B.Cu | In<n>.Cu",+    "neg": "the negative net",+    "pos": "the positive net",+    "rule": "bool, default true - add a width rule for the two nets (+/-10%) so the board's own width rule does not flag the pair",+    "to": "{pos, neg} the destination pads",+    "via": "[[x,y]...] centreline waypoints mm (optional)",+    "width": "mm"+   },+   "longRunning": false,+   "name": "altium_route_pair",+   "output": {+    "gapMm": "number",+    "lengthsMm": "{pos, neg}",+    "revision": "string",+    "sizedFor": "the impedance row used | null",+    "skewMm": "number",+    "widthMm": "number"+   },+   "pitfalls": [+    "One layer per call; split a pair that changes layer into two calls at the via pair.",+    "The waypoints are the pair's centreline, so keep them clear of other copper by half the pair's width."+   ],+   "related": [+    "altium_route_net",+    "altium_impedance",+    "altium_diff_pairs",+    "altium_net_lengths",+    "altium_add_rule"+   ],+   "statusVerb": null,+   "summary": "Route a differential pair as a pair: one centreline from the source pads' midpoint through waypoints to the destination pads' midpoint, each net offset to its own side at width + gap with mitred corners and a short fan-out from each pad; width and gap given or solved from the stack-up for a target impedance; both lengths and the skew answered.",+   "timeoutSeconds": 120+  },+  {+   "example": {+    "projectPath": "C:/proj/Board.PrjPcb"+   },+   "hint": "Fix on the sheets (altium_sch_connect, altium_sch_place_part for a missing capacitor), then altium_update_pcb. altium_run_erc is Altium's own ERC with its rules.",+   "input": {+    "projectPath": "the .PrjPcb (its sheets are read from file; the netlist is Altium's compile)"+   },+   "longRunning": false,+   "name": "altium_schematic_review",+   "output": {+    "decoupling": "[{net, ics, capacitors, ok}]",+    "errors": "[{check, pin|net, ...}]",+    "nets": "int",+    "ok": "bool - no errors",+    "pins": "int",+    "sheets": "[path]",+    "warnings": "[{check, ...}]"+   },+   "pitfalls": [+    "Pins named NC, N/C or DNC are expected to float.",+    "Ground nets are recognised by name (GND, AGND, DGND, PGND, VSS, 0V, *_GND)."+   ],+   "related": [+    "altium_netlist",+    "altium_read_sheet_file",+    "altium_run_erc",+    "altium_sch_connect",+    "altium_update_pcb"+   ],+   "statusVerb": null,+   "summary": "Review the schematic before Update PCB: every placed pin (read from the sheet files, with its electrical type) against Altium's compiled netlist. Errors: power pins on no net, outputs driving each other. Warnings: pins on no net, one-pin nets, inputs nothing drives, IC supplies with no decoupling capacitor to ground.",+   "timeoutSeconds": 300+  },+  {+   "example": {+    "pcbPath": "C:/proj/Board.PcbDoc",+    "projectPath": "C:/proj/Board.PrjPcb"+   },+   "hint": "Fix what issues lists and run it again; when ready, upload the zip, then the BOM and CPL for assembly and check rotations in JLCPCB's preview.",+   "input": {+    "outputDir": "the order folder (default 'JLCPCB order <board>' beside the project; emptied first)",+    "pcbPath": "its .PcbDoc",+    "projectPath": "the .PrjPcb",+    "variant": "an assembly variant for the BOM (optional)"+   },+   "longRunning": false,+   "name": "altium_jlcpcb_order",+   "output": {+    "assemblyService": "economic | standard | null",+    "bomFiles": "[..] incl. bom-jlcpcb.csv, cpl-jlcpcb.csv",+    "folder": "path",+    "gerberZip": "{path, files}",+    "issues": "[{from, what, at?}]",+    "orderForm": "{layers, dimensionsMm, outerCopperOz, minTrackSpaceMm, minHoleMm}",+    "ready": "bool - fab fits, an assembly tier passes, no issues"+   },+   "pitfalls": [+    "Runs Silkscreen Preparation on the board (through export_manufacturing) and writes the order folder fresh each time.",+    "Takes a minute or two: the manufacturing export is most of it."+   ],+   "related": [+    "altium_fab_check",+    "altium_assembly_check",+    "altium_export_manufacturing",+    "altium_bom",+    "altium_panelize"+   ],+   "statusVerb": null,+   "summary": "A JLCPCB order in one call: the board checked against JLCPCB's fab limits, the Gerbers and drill zipped the way JLCPCB's order page takes them, the JLCPCB BOM and pick-and-place (CPL), the assembly-tier check, and the order-form values, in one folder with every issue in one list.",+   "timeoutSeconds": 900+  },+  {+   "example": {+    "filePath": "C:/proj/Board.PcbDoc",+    "projectPath": "C:/proj/Board.PrjPcb"+   },+   "hint": "Fix what the rules mark false: panelize a small board (altium_panelize, then panelMm), add fiducials (altium_board_features), find LCSC numbers (adom-parts-search). Then altium_bom (JLCPCB BOM) and altium_export_manufacturing (pick and place), and check every part's rotation in JLCPCB's preview.",+   "input": {+    "filePath": "the .PcbDoc (opened first; else the current board)",+    "panelMm": "[w, h] when it ships in a panel (altium_panelize's panelMm)",+    "projectPath": "the .PrjPcb - checks every purchased BOM line has an LCSC number (altium_bom)"+   },+   "longRunning": false,+   "name": "altium_assembly_check",+   "output": {+    "board": "{parts, bottomSideParts, finestPitch, smallestChip, tinyFootprints, fiducials, throughHoleParts}",+    "economic": "{ok, rules:[{rule, ok, detail}]}",+    "linesWithoutLcsc": "[..]",+    "service": "economic | standard | null",+    "standard": "{ok, rules}"+   },+   "pitfalls": [+    "Pitch is the closest pad centres within one part of 3 or more SMD pads; package size is read from footprint names and two-pad parts' pad spacing.",+    "Rotations are not checked: JLCPCB's preview is the authority on orientation."+   ],+   "related": [+    "altium_bom",+    "altium_export_manufacturing",+    "altium_panelize",+    "altium_board_features",+    "altium_fab_check"+   ],+   "statusVerb": null,+   "summary": "Ready for JLCPCB assembly? The board against both JLCPCB PCBA tiers as published (Economic and Standard): board or panel size, thickness and layers, parts on one or both sides, the smallest package and the finest pin pitch, fiducials, and the parts with no LCSC number; answers the cheapest tier every rule allows.",+   "timeoutSeconds": 300+  },+  {+   "example": {+    "edge": true,+    "freqGHz": 2.4,+    "nets": [+     "ANT_RF"+    ],+    "viaNet": "GND"+   },+   "hint": "Then altium_repour so the pours join the vias, and altium_run_drc. altium_return_path shows where a net still runs over a gap.",+   "input": {+    "clearance": "to other copper, mm, default 0.2",+    "drill": "mm, default 0.3",+    "dryRun": "bool - plan only",+    "edge": "bool - a fence round the board edge too",+    "edgeInsetMm": "edge fence: distance in from the edge (default via radius + 0.5)",+    "expectedRevision": "optional",+    "filePath": "the .PcbDoc (opened first; else the current board)",+    "freqGHz": "the highest frequency: pitch = lambda/20 in FR-4",+    "nets": "[net] - the tracks to fence (e.g. [ANT_RF])",+    "offsetMm": "track centreline to via centre (default half the widest track + clearance + via radius + 0.1)",+    "pitchMm": "pitch, default 2 (a cap when freqGHz is given)",+    "size": "via diameter mm, default 0.6",+    "viaNet": "the net the vias tie to (usually GND), required"+   },+   "longRunning": false,+   "name": "altium_via_fence",+   "output": {+    "applied": "int",+    "failed": "int",+    "planned": "{vias, candidates, pitchMm, offsetMm, points}"+   },+   "pitfalls": [+    "Vias go through (top to bottom); a pour on viaNet should exist on the layers they join.",+    "Spots too close to other copper, pads or the edge are skipped, not forced."+   ],+   "related": [+    "altium_return_path",+    "altium_board_features",+    "altium_repour",+    "altium_run_drc",+    "altium_apply_pours"+   ],+   "statusVerb": null,+   "summary": "A via fence for EMC: a row of ground vias on both sides of RF or fast nets' tracks, and/or round the board edge, at a pitch from the frequency (lambda/20) or given; each via clear of other copper and pads, the same net's vias and the edge.",+   "timeoutSeconds": 180+  },+  {+   "example": {+    "size": 1.0+   },+   "hint": "Then altium_silkscreen_prep (it clips silk off the mask) and altium_fab_check {fab:\"jlcpcb\"} for text height and line width. For unplaced parts try refs with a smaller size, or move the part.",+   "input": {+    "dryRun": "bool - plan only",+    "filePath": "the .PcbDoc (opened first; else the current board)",+    "gap": "label to part, mm, default 0.2",+    "minSize": "mm - parts with no spot at size step down by 0.1 to this (default: size, no shrinking)",+    "padClearance": "label to any pad, mm, default 0.15",+    "refs": "[ref] - only these (default every part)",+    "size": "text height mm, default 1.0 (JLCPCB's minimum)"+   },+   "longRunning": false,+   "name": "altium_silk_designators",+   "output": {+    "onPadsAfter": "[{ref, pad}] - must be empty",+    "onPadsBefore": "[{ref, pad}]",+    "placed": "int",+    "planned": "dryRun: [{ref, x, y, size, rotation, side, at}]",+    "unplaced": "[{ref, why}]"+   },+   "pitfalls": [+    "Labels are tried above, below, right, left and at the corners of the part's body and pads, big parts first.",+    "Bottom-side labels are mirrored by Altium; onPadsAfter checks them with Altium's own boxes."+   ],+   "related": [+    "altium_silk_state",+    "altium_apply_silkscreen",+    "altium_silkscreen_prep",+    "altium_fab_check"+   ],+   "statusVerb": null,+   "summary": "Place every reference designator the way assembly houses want: one size, upright, beside its own part, clear of every pad (JLCPCB: 0.15 mm), every other part's body and the other designators, inside the board; read back from Altium's own text boxes. Parts with no clear spot are reported, not forced onto pads.",+   "timeoutSeconds": 600+  },+  {+   "example": {},+   "hint": "Pour the lighter layer of an unbalanced pair (altium_apply_pours: a GND pour, or copper thieving) or ask the fab for thieving; then run it again.",+   "input": {+    "filePath": "the .PcbDoc (opened first; else the current board)",+    "resolutionMm": "raster cell, default 0.1"+   },+   "longRunning": false,+   "name": "altium_copper_balance",+   "output": {+    "balanced": "bool",+    "boardMm2": "number",+    "layers": "[{layer, copperMm2, percent, plane?}]",+    "pairs": "[{layers, percent, differencePoints, ok}] - layer 1 with n, 2 with n-1...",+    "poursWithoutRegions": "[{net, layer}] - pours not poured, or hatched from tracks (counted through their tracks)"+   },+   "pitfalls": [+    "A raster of the live board's copper: within a cell of the true area (0.1 mm default).",+    "A pour counts only once poured (regions); a hatched pour built from tracks is counted through its tracks only when the board lists them."+   ],+   "related": [+    "altium_apply_pours",+    "altium_pour_state",+    "altium_layer_stack",+    "altium_fab_check"+   ],+   "statusVerb": null,+   "summary": "How much of each copper layer is copper (tracks, pads, vias and what Altium actually poured, inside the outline; a plane counts full), and how far apart each mirrored pair of layers is: the copper balance a fab asks about because an unbalanced stack bows and twists in reflow.",+   "timeoutSeconds": 180+  },+  {+   "example": {+    "nets": [+     "USB_DP",+     "USB_DN",+     "CLK"+    ]+   },+   "hint": "Extend or join the pour under the net (altium_apply_pours), reroute it over solid copper (altium_route_net), or add a stitching via or capacitor where it changes reference.",+   "input": {+    "filePath": "the .PcbDoc (opened first; else the current board)",+    "nets": "[net] - only these (default every signal net)",+    "referenceNets": "[net] - the reference nets (default: every net that owns a pour or a plane)",+    "resolutionMm": "raster cell and sampling step, default 0.1"+   },+   "longRunning": false,+   "name": "altium_return_path",+   "output": {+    "layersWithNoReferenceCopper": "[layer]",+    "nets": "[{net, layer, lengthMm, unreferencedMm, percent, firstGapAt}] worst first",+    "ok": "bool",+    "referenceNets": "[net]"+   },+   "pitfalls": [+    "Reference copper is pours, planes, the reference nets' own copper and net-less copper (a hatched pour's tracks): another signal's track under a net is not a reference.",+    "Checks the two neighbouring copper layers; a board with no pours or planes has nothing to reference (layersWithNoReferenceCopper)."+   ],+   "related": [+    "altium_apply_pours",+    "altium_pour_state",+    "altium_route_net",+    "altium_net_lengths",+    "altium_board_features"+   ],+   "statusVerb": null,+   "summary": "Where signal tracks lose their reference: for every signal net, the length of track on each layer that runs over no pour or plane copper on either neighbouring layer (a split, a gap, a void), with where the first gap starts. The return current detours around those gaps: loop area, emissions, crosstalk.",+   "timeoutSeconds": 180+  },+  {+   "example": {+    "maxBottomMm": 2,+    "maxTopMm": 8,+    "minGapMm": 0.25,+    "regions": [+     {+      "maxMm": 3,+      "name": "display boss",+      "rect": [+       10,+       10,+       30,+       25+      ]+     }+    ]+   },+   "hint": "Move or swap what overLimit and regionHits name (altium_move_parts, altium_swap_footprint); separate colliding parts. Parts in noBody need a body (altium_add_body) before the check means anything for them.",+   "input": {+    "filePath": "the .PcbDoc (opened first; else the current board)",+    "limit": "how many of the tallest parts to list (default 25)",+    "maxBottomMm": "limit below the bottom side",+    "maxTopMm": "height limit above the top side",+    "minGapMm": "report bodies closer than this (default 0: collisions only)",+    "regions": "[{rect:[x1,y1,x2,y2] mm, maxMm, side?: top|bottom, name?}] - lid features over part of the board"+   },+   "longRunning": false,+   "name": "altium_height_check",+   "output": {+    "collisions": "[{parts, overlapMm, at}]",+    "heights": "[{ref, side, heightMm, rect}]",+    "noBody": "[ref] - parts with no 3D body (no height)",+    "ok": "bool",+    "overLimit": "[{ref, side, heightMm, limitMm, overByMm}]",+    "regionHits": "[{region, ref, heightMm, maxMm}]",+    "tallest": "{top, bottom}",+    "tooClose": "[{parts, gapMm, at}]"+   },+   "pitfalls": [+    "Bodies are compared as boxes, each body its own box; Altium's Component Clearance DRC checks the real shapes.",+    "A height is the body's top above the surface its part sits on; the board's own thickness is not included."+   ],+   "related": [+    "altium_add_body",+    "altium_move_parts",+    "altium_find_space",+    "altium_run_drc",+    "altium_show_3d_board"+   ],+   "statusVerb": null,+   "summary": "Will it fit the enclosure? Each part's height from its 3D bodies (top and bottom side), the parts over a height limit board-wide or under a lid feature in a region, and part bodies that collide or come closer than a gap.",+   "timeoutSeconds": 120+  },+  {+   "example": {+    "nets": {+     "GND": 3,+     "SW": 3,+     "VIN": 3+    },+    "riseC": 10+   },+   "hint": "Widen what shortOn names (altium_set_track_width, altium_add_rule kind width) or pour the net (altium_apply_pours); a layer change needs neededPerLayerChange vias side by side (altium_place_vias).",+   "input": {+    "filePath": "the .PcbDoc (opened first; else the current board)",+    "nets": "{net: amps}, e.g. {VIN: 3, GND: 3}",+    "platingUm": "via barrel plating, default 25",+    "riseC": "allowed temperature rise, default 10"+   },+   "longRunning": false,+   "name": "altium_current_capacity",+   "output": {+    "nets": "[{net, amps, layers:[{layer, copperUm, narrowestMm, at, neededMm, ok}], shortOn, ok, vias:{count, smallestDrillMm, ampsEach, neededPerLayerChange}, poursOn}]",+    "ok": "bool"+   },+   "pitfalls": [+    "IPC-2221 (conservative, the chart fabs quote); IPC-2152 allows more on inner layers next to planes.",+    "Reads tracks and arcs: a pour on the same layer (poursOn) may already carry the current."+   ],+   "related": [+    "altium_routing_state",+    "altium_layer_stack",+    "altium_set_track_width",+    "altium_add_rule",+    "altium_apply_pours",+    "altium_place_vias"+   ],+   "statusVerb": null,+   "summary": "Will this trace carry the current? For each power net and its current: the narrowest track on every layer it is routed on against the width IPC-2221 needs for the allowed temperature rise (with each layer's real copper weight), and what one of its vias carries.",+   "timeoutSeconds": 120+  },+  {+   "example": {+    "apply": {+     "name": "USB 90",+     "scope": "InDifferentialPair('USB')"+    },+    "diff": 90,+    "z0": 50+   },+   "hint": "Then altium_add_rule or apply writes the rules; altium_diff_pairs lists the pairs to scope them to. Tell the fab the target impedance: it tunes the stack-up with its own solver.",+   "input": {+    "apply": "{scope: Altium query e.g. InNetClass('RF') or InDifferentialPair('USB'), name?} - one rule per layer at +/-10%",+    "diff": "differential ohm, e.g. 90 or 100",+    "filePath": "the .PcbDoc (opened first; else the current board)",+    "gapMm": "fix the pair gap (default: gap = width)",+    "layers": "[layer names] (default every copper layer)",+    "z0": "single-ended ohm (default 50 when diff is not given)"+   },+   "longRunning": false,+   "name": "altium_impedance",+   "output": {+    "applied": "[{layer, rule, success}] | null",+    "layers": "[{layer, model, copperUm, dielectricMm:[above, below], width, z0, diffWidth, diffGap, zdiff}]"+   },+   "pitfalls": [+    "Closed-form models (Hammerstad-Jensen, Wheeler, IPC-2141 coupling): within a few percent of a field solver on FR-4; uncoated (mask lowers outer layers 1 to 3 ohm).",+    "Inner layers assume planes on both neighbours."+   ],+   "related": [+    "altium_layer_stack",+    "altium_add_rule",+    "altium_diff_pairs",+    "altium_net_classes"+   ],+   "statusVerb": null,+   "summary": "Controlled impedance from the board's own stack-up: the track width each copper layer needs for a single-ended target (50 ohm default) and the width and gap for a differential pair (90 ohm USB, 100 ohm Ethernet/LVDS), microstrip outside and stripline inside; optionally written as width or diff-pair rules per layer.",+   "timeoutSeconds": 120+  },+  {+   "example": {+    "cols": 3,+    "panelPath": "C:/proj/Panel.PcbDoc",+    "pcbPath": "C:/proj/Board.PcbDoc",+    "projectPath": "C:/proj/Board.PrjPcb",+    "railMm": 5,+    "rows": 3,+    "separation": "vscore"+   },+   "hint": "Fix what jlcpcb.rules marks false (panel under 70 mm: more rows or wider rails; copper under 0.4 mm from a cut; rounded corners on a V-cut) and run it again with overwrite:true. Then altium_export_manufacturing {projectPath, pcbPath: the panel}.",+   "input": {+    "cols": "default 2",+    "gapMm": "between boards, default 2 (vscore: 0)",+    "overwrite": "bool - replace an existing panelPath (closed in Altium unsaved first); without it an existing file is refused",+    "panelPath": "the new panel .PcbDoc",+    "pcbPath": "the board's .PcbDoc",+    "projectPath": "a .PrjPcb the panel joins (so altium_export_manufacturing can make its Gerbers)",+    "railMm": "rail width, default 5 (0 for none)",+    "rows": "default 1",+    "separation": "none (default) | vscore (boards edge to edge, V-CUT lines across the panel on Mechanical 1 with 'V-CUT' written at both ends, as JLCPCB asks) | tabs (mouse bites: 0.6 mm holes five to a tab, the rout path drawn on Mechanical 1)",+    "toolingHoles": "default true: 2 mm unplated holes and 1 mm fiducials when rails are 5 mm or wider"+   },+   "longRunning": false,+   "name": "altium_panelize",+   "output": {+    "boardMm": "[w, h] of one board's outline",+    "boardsAt": "[[x, y]] each board's outline corner from the panel's corner",+    "jlcpcb": "{rules:[{rule, ok (true|false|null = a note), detail}], fits}",+    "nearestCopperToEdge": "{mm, at}",+    "panelMm": "[w, h]",+    "panelPath": "path",+    "project": "{path, added}",+    "saved": "bool",+    "separation": "{kind, cutsX, cutsY | tabs, holes, routLines, applied, failed}",+    "warnings": "[{copperOutsideOutline}]"+   },+   "pitfalls": [+    "Opens the board, then creates and saves the panel; without projectPath the saved panel is closed (open it with altium_open_document).",+    "tabs refuses a board that draws its own outline on a mechanical layer: every copy brings it along closed and a router would cut each board free.",+    "JLCPCB's rules as published (V-cut standards and mouse-bite guide, read 2026-10-23): V-cut panels 70 to 475 mm, 0.6 mm boards or thicker, copper 0.4 mm (1.0 recommended) from a cut, at most 25 cuts a side; mouse bites 0.6 mm holes 5 to a set, gaps of 1.2 mm or more."+   ],+   "related": [+    "altium_fab_check",+    "altium_export_manufacturing",+    "altium_board_features",+    "altium_set_outline"+   ],+   "statusVerb": null,+   "summary": "Step and repeat a board into a manufacturing panel: a new .PcbDoc holding Altium's own embedded board array of the board (rows x cols), with rails, the panel frame on the outline layer, three tooling holes and three fiducials; boards separated by JLCPCB-style V-score lines or mouse-bite tabs, with JLCPCB's rules checked. The array stays linked to the board.",+   "timeoutSeconds": 240+  },+  {+   "example": {+    "fab": "jlcpcb"+   },+   "hint": "Look up the fab's own limits (its capability page) and pass them: the bridge does not carry fab numbers, they change. The measured values are what a fab's order form asks for.",+   "input": {+    "copperOz": "outer copper weight for the preset (default: read from the stack)",+    "fab": "jlcpcb - JLCPCB's published standard capabilities for this board's layer count and copper weight (source and date in the reply)",+    "filePath": "the .PcbDoc (opened first)",+    "limits": "{minTrack, minClearance, minViaDrill, minViaDiameter, minViaAnnularRing, minPadHole, minPadAnnularRing, minSmdPad, minEdgeClearance, minHoleToHoleVia, minHoleToHolePad, maxDrill, minSilkLine, minSilkText, maxLayers, minBoardMm:[w,h], maxBoardMm:[w,h]} in mm - any fab's numbers; with fab, these override the preset"+   },+   "longRunning": false,+   "name": "altium_fab_check",+   "output": {+    "costlierOrBelowRecommended": "[{feature, boardMm, note}]",+    "fab": "{name, service, copperOz, source, read, notes} | null",+    "fits": "bool | null",+    "measured": "{track, viaDrill, viaDiameter, viaAnnularRing, padHole, padAnnularRing, smdPad, edgeClearance, holeToHoleVia, holeToHolePad, largestDrill, silkText, silkLine: {mm, at}, clearanceRule, copperLayers, boardSize, counts}",+    "outsideLimits": "[{limit, limitMm, boardMm, at}]"+   },+   "pitfalls": [+    "Measures the board as it is now, from the live document: routed copper, vias and pads.",+    "Board size is the outline's bounding box."+   ],+   "related": [+    "altium_routing_state",+    "altium_read_rules",+    "altium_layer_stack",+    "altium_export_manufacturing",+    "altium_run_drc"+   ],+   "statusVerb": null,+   "summary": "Before ordering: the board's fab-relevant minimums measured from the live board (narrowest track, smallest via drill, diameter and annular ring, smallest pad hole and ring, smallest SMD pad, clearance rule, copper layers, board size), each with where it is; with a fab's limits, which ones fall short.",+   "timeoutSeconds": 180+  },+  {+   "example": {+    "params": true,+    "path": "C:\\\\work\\\\Power.SchDoc"+   },+   "hint": "The saved file, not the live sheet: an open Altium's unsaved edits are not in it (altium_schematic_data reads those). Useful when Altium is closed, busy or slow, and to check UniqueIDs before Update PCB.",+   "input": {+    "params": "bool, default false - every parameter with its value",+    "path": "the .SchDoc (alias filePath)",+    "pins": "bool, default true - each part's own pins plus the shared part 0"+   },+   "longRunning": false,+   "name": "altium_read_sheet_file",+   "output": {+    "components": "[{designator, libReference, comment, description, uniqueId, partId, partCount, xMil, yMil, orientation, sourceLibrary, params?, pins?}]",+    "count": "int",+    "elapsedMs": "int",+    "format": "the file header"+   },+   "pitfalls": [+    "Binary .SchDoc only (Altium's default); an ASCII-format sheet is refused by name.",+    "No connectivity: nets come from altium_netlist."+   ],+   "related": [+    "altium_schematic_data",+    "altium_part_parameters",+    "altium_bom",+    "altium_netlist"+   ],+   "statusVerb": null,+   "summary": "Read a schematic sheet straight from its .SchDoc file, without Altium (it need not be installed or running): every part with its designator, library reference, comment, UniqueID, part number, position, parameters and pins, in milliseconds.",+   "timeoutSeconds": 30+  },+  {+   "example": {+    "members": [+     "U1",+     "R1",+     "C1"+    ],+    "name": "CH1",+    "room": true+   },+   "hint": "Use a class per channel or block: rules scope with InComponentClass('<name>'), and the room shows the block's area to the user and to DRC.",+   "input": {+    "filePath": "the .PcbDoc (opened first)",+    "members": "[designators]",+    "name": "class name (omit to list)",+    "remove": "bool - delete the class and its room",+    "room": "true (fit around the members' pads, 1 mm margin) | {marginMm} | {rect: [x1, y1, x2, y2] mm}"+   },+   "longRunning": false,+   "name": "altium_component_classes",+   "output": {+    "classes": "[{name, members}] (list)",+    "members": "[designator]",+    "missing": "[designator not on the board]",+    "room": "Altium's description of the room rule",+    "roomMm": "[x1, y1, x2, y2]"+   },+   "pitfalls": [+    "The room is a confinement rule ('Adom room <name>', top layer): a part outside it is a DRC violation, not a move.",+    "The same name replaces the class and its room."+   ],+   "related": [+    "altium_net_classes",+    "altium_replicate_layout",+    "altium_add_rule",+    "altium_find_space"+   ],+   "statusVerb": null,+   "summary": "List, make or remove component classes on the board, each optionally with a room: a placement area that keeps its parts together (fitted around the parts or a rectangle). The classes scope rules and group a channel for replicate_layout.",+   "timeoutSeconds": 120+  },+  {+   "example": {+    "filePath": "C:\\\\work\\\\Board.PcbDoc"+   },+   "hint": "Add teardrops after routing and before the final pour and DRC: altium_repour, then altium_run_drc.",+   "input": {+    "filePath": "the .PcbDoc (opened first)",+    "force": "bool, default false: add even where a teardrop breaks a rule",+    "remove": "bool, default false (add)",+    "save": "bool, default true",+    "selectedOnly": "bool, default false (all)",+    "smdPads": "bool, default true",+    "tJunctions": "bool, default true",+    "tracks": "bool, default true",+    "vias": "bool, default true"+   },+   "longRunning": false,+   "name": "altium_teardrops",+   "output": {+    "dialogsLeft": "[anything Altium asked]",+    "steps": "[mode, objects, toggles, OK]",+    "teardropsAfter": "int",+    "teardropsBefore": "int"+   },+   "pitfalls": [+    "Altium's own sizes are used (via 70% width / 30% length, pads 200% / 100%, curved).",+    "force:true places teardrops that violate clearance: leave it off unless the user asks."+   ],+   "related": [+    "altium_repour",+    "altium_run_drc",+    "altium_routing_state",+    "altium_selection"+   ],+   "statusVerb": null,+   "summary": "Add (or remove) teardrops with Altium's own Tools > Teardrops: on vias and through-hole pads, SMD pads, track width changes and T-junctions, for the whole board or the selection; teardrops counted before and after.",+   "timeoutSeconds": 300+  },+  {+   "example": {+    "groups": [+     {+      "nets": [+       "D0",+       "D1",+       "D2",+       "D3"+      ],+      "toleranceMm": 0.25+     }+    ]+   },+   "hint": "Lengthen the short nets of a group with altium_route_net (a detour) until spreadMm is within tolerance; Altium's interactive length tuning is the hand alternative.",+   "input": {+    "filePath": "the .PcbDoc (opened first)",+    "groups": "[{nets:[..], toleranceMm?: 0.5}] - matched-length groups (a DDR byte lane, a parallel bus)",+    "nets": "[net] - default every routed net",+    "pairs": "[{name?, pos, neg}] - default the board's differential pairs"+   },+   "longRunning": false,+   "name": "altium_net_lengths",+   "output": {+    "groups": "[{nets:[{net, lengthMm, shortByMm}], spreadMm, withinTolerance, longest}]",+    "groupsOutOfTolerance": "int",+    "nets": "[{net, lengthMm, byLayer, vias, segments}]",+    "pairs": "[{name, pos, neg, posMm, negMm, skewMm}]"+   },+   "pitfalls": [+    "A net with branches sums every branch: a multi-pin net's length is its total copper, not pin to pin.",+    "Vias are counted, not added to the length."+   ],+   "related": [+    "altium_routing_state",+    "altium_route_net",+    "altium_diff_pairs",+    "altium_add_rule"+   ],+   "statusVerb": null,+   "summary": "Routed length of every net (or the ones named) on the live board, by layer with its vias; the skew inside each differential pair; and the spread of matched-length groups against a tolerance.",+   "timeoutSeconds": 180+  },+  {+   "example": {+    "filePath": "C:\\\\work\\\\Board.PcbDoc"+   },+   "hint": "Run it before fab outputs and before 3D or screenshot renders: fabs trim silk off pads anyway, but a board that shows ink on copper looks wrong.",+   "input": {+    "check": "bool, default true: DRC before and after",+    "clipLocked": "bool, default false",+    "deleteOutsideBoard": "bool, default true",+    "filePath": "the .PcbDoc (opened first)",+    "minRemainingMil": "shortest silk segment kept (Altium's default 5.9 mil)",+    "moveText": "bool, default true: move text off pads (within Altium's Max Distance) instead of clipping it",+    "save": "bool, default true",+    "selectedOnly": "bool, default false"+   },+   "longRunning": false,+   "name": "altium_silkscreen_prep",+   "output": {+    "after": "{same}",+    "before": "{silkToSolderMask, silkToSilk, silkToBoardRegion, maskSliver}",+    "dialogsLeft": "[anything Altium asked]",+    "silkOffCopper": "bool",+    "steps": "[toggles, OK]"+   },+   "pitfalls": [+    "It uses the board's SilkToSolderMaskClearance rule (Use Design Rules stays on).",+    "Text that cannot move within Max Distance stays and is counted in after.silkToSolderMask."+   ],+   "related": [+    "altium_silk_state",+    "altium_apply_silkscreen",+    "altium_run_drc",+    "altium_export_manufacturing"+   ],+   "statusVerb": null,+   "summary": "Clean the silkscreen off the copper with Altium's own Tools > Silkscreen Preparation: silk clipped clear of every mask opening (pad) by the board's silk rule, designators moved off pads where they fit, silk outside the board deleted; the silkscreen DRC hits counted before and after.",+   "timeoutSeconds": 600+  },+  {+   "example": {+    "expect": {+     "GND": [+      "U1.4",+      "U10.4"+     ],+     "VBUS": [+      "U1.1",+      "U10.1"+     ]+    },+    "projectPath": "C:\\\\work\\\\Board.PrjPcb"+   },+   "hint": "Use it to check a schematic the AI drew or edited before altium_update_pcb: expect the nets you meant and read mismatches.",+   "input": {+    "expect": "optional {net: [\"U1.3\", ...]}: compare pin by pin",+    "net": "optional: one net",+    "projectPath": ".PrjPcb (required)"+   },+   "longRunning": false,+   "name": "altium_netlist",+   "output": {+    "compileErrors": "[messages]",+    "mismatches": "[{net, missing, extra} | {net, unexpected}] (expect)",+    "netCount": "int",+    "nets": "{net: [pin]}"+   },+   "pitfalls": [+    "Unnamed nets read as Altium names them (NetU2_3).",+    "Compiles the project first: the reply is the design as Altium sees it now."+   ],+   "related": [+    "altium_compile",+    "altium_schematic_data",+    "altium_sch_connect",+    "altium_sch_sheet_symbol",+    "altium_update_pcb"+   ],+   "statusVerb": null,+   "summary": "The compiled netlist of a project: every net with its pins (Designator.Pin) across every sheet, the hierarchy resolved; or one net; or compare with the nets you expect.",+   "timeoutSeconds": 300+  },+  {+   "example": {+    "child": "Power.SchDoc",+    "entries": [+     {+      "io": "input",+      "name": "VIN",+      "net": "VBUS"+     },+     {+      "io": "output",+      "name": "3V3",+      "net": "3V3"+     },+     {+      "io": "passive",+      "name": "GND",+      "net": "GND"+     }+    ],+    "projectPath": "C:\\work\\Board.PrjPcb",+    "sheet": "C:\\work\\Top.SchDoc"+   },+   "hint": "Then connect the child's circuit to its ports (altium_sch_connect kind:'port', or a wire to stubEndMil) and altium_compile {projectPath}: entries and ports join by name.",+   "input": {+    "child": "the child .SchDoc (required; a name alone sits beside the parent)",+    "entries": "[{name, io: input|output|bidirectional|passive, side?: left|right (default inputs left), net?: a parent net to join (a stub and a net label), stubMil?: 300}]",+    "name": "the symbol's designator (default the child's name)",+    "ports": "bool, default true: put a port on the child for each entry it lacks",+    "portsAt": "{left, right, top} mil on the child (defaults 1000, 15000, 9500)",+    "projectPath": ".PrjPcb: lists the child when it is not listed yet",+    "save": "bool, default true",+    "sheet": "the parent .SchDoc (required)",+    "widthMil": "default 1400",+    "x": "mil, the symbol's top-left corner (default 2000)",+    "y": "mil (default 8000)"+   },+   "longRunning": false,+   "name": "altium_sch_sheet_symbol",+   "output": {+    "childCreated": "bool",+    "ports": "{placed:[{name, io, atMil, stubEndMil}], alreadyThere:[name]}",+    "steps": "[childCreated | addedToProject]",+    "symbol": "{name, file, atMil, sizeMil, entries:[{name, side, io, net, atMil}]}"+   },+   "pitfalls": [+    "Altium never places a sheet entry by itself (its position stays 0,0 and it draws nowhere): the bridge puts each one on the symbol's edge, 100 mil apart.",+    "Ports already on the child (by name) are reported in alreadyThere, not doubled."+   ],+   "related": [+    "altium_sch_connect",+    "altium_compile",+    "altium_schematic_data",+    "altium_build_schematic"+   ],+   "statusVerb": null,+   "summary": "Make a design hierarchical: a sheet symbol on the parent sheet for a child sheet, its entries joined to the parent's nets, and matching ports on the child. A child that does not exist yet is made from Altium's sheet template and added to the project.",+   "timeoutSeconds": 240+  },+  {+   "example": {+    "fiducials": [+     {+      "x": 5,+      "y": 5+     },+     {+      "x": 45,+      "y": 25+     }+    ],+    "mountingHoles": {+     "corners": {+      "inset": 4+     },+     "drill": 3.2+    },+    "stitching": {+     "net": "GND",+     "pitch": 4+    }+   },+   "hint": "dryRun first to see the stitching grid. After stitching, altium_repour so the net's pours join through the vias, then altium_run_drc.",+   "input": {+    "dryRun": "bool - plan only",+    "expectPath": "optional",+    "expectedRevision": "optional",+    "fiducials": "[{x, y, size?: 1, opening?: 2 (mask opening diameter), side?: top|bottom}]",+    "filePath": "the .PcbDoc (opened first; expectPath is opened too)",+    "keepouts": "[{rect: [x1, y1, x2, y2] mm, layer?: all (the Keep-Out layer, default) | F.Cu | B.Cu | In<n>.Cu}] - stitching in the same call keeps out of them",+    "mountingHoles": "[{x, y, drill?: 3.2, plated?: false, pad?: drill+2.8 when plated, net?}] or {corners: {inset: 4}, drill?, plated?, net?}",+    "stitching": "{net (required), pitch?: 5, size?: 0.6, drill?: 0.3, clearance?: 0.3, edge?: 1 (from the outline), region?: [[x,y],..], max?: 400, underParts?: false (vias stay out of each part's pad box)}"+   },+   "longRunning": false,+   "name": "altium_board_features",+   "output": {+    "applied": "int",+    "planned": "{mountingHoles, fiducials, stitching:{count, points}}",+    "results": "[ok|err:..] per item",+    "state": "routing_state after"+   },+   "pitfalls": [+    "Stitching keeps the pad clearance conservative (a pad's circumscribed circle) and does not look at pours: pours of other nets repour around the vias.",+    "Unplated holes are bare holes (pad = drill); plated holes take a ring and can join a net (e.g. GND)."+   ],+   "related": [+    "altium_place_vias",+    "altium_repour",+    "altium_run_drc",+    "altium_routing_state"+   ],+   "statusVerb": null,+   "summary": "Add the furniture every real board needs in one undo step: mounting holes (at points or the four corners), fiducials, keepout areas, and a stitching-via grid on a net that keeps clear of every other net's copper, every pad and the board edge.",+   "timeoutSeconds": 180+  },+  {+   "example": {+    "clearanceMm": 0.3,+    "near": "U1",+    "part": "C10"+   },+   "hint": "Move the part with altium_move_parts {parts:[{ref, x, y, rotation}]} to a spot, then altium_routing_drc. Extents follow Altium's Component Clearance rule: 3D bodies and pads, or every primitive but text for a part with no body.",+   "input": {+    "check": "bool - report overlaps instead",+    "clearanceMm": "default 0.25",+    "copper": "bool, default true - avoid that side's tracks and vias",+    "count": "default 5",+    "edgeMm": "from the outline, default 0.3",+    "near": "designator to place beside (or at: {x, y} mm)",+    "part": "designator of the part to place (its own size and origin), or sizeMm: [w, h]",+    "precise": "bool, with check - also run Altium's batch DRC and list its Component Clearance violations (primitive to primitive)",+    "radiusMm": "search radius, default 10",+    "side": "top | bottom (default the part's)"+   },+   "longRunning": false,+   "name": "altium_find_space",+   "output": {+    "outsideOutline": "[designator] (check)",+    "overlaps": "[[a, b]] (check)",+    "spots": "[{x, y (the part origin, for move_parts), distanceMm, extent}]"+   },+   "pitfalls": [+    "A part with no 3D body is measured by its silkscreen outline too, as Altium does: dense boards report silk-to-silk overlaps.",+    "Rotations are kept: rotate first if the part should sit another way."+   ],+   "related": [+    "altium_move_parts",+    "altium_routing_state",+    "altium_routing_drc"+   ],+   "statusVerb": null,+   "summary": "Free spots on the board for a part near another (clear of every other part on that side, of that side's tracks and vias, and inside the outline), nearest first; or check:true lists parts that already overlap or leave the outline.",+   "timeoutSeconds": 180+  },+  {+   "example": {},+   "hint": "Lets the user point at parts in Altium and say 'these': move them with altium_move_parts {ref, dx, dy}, replicate them with altium_replicate_layout.",+   "input": {+    "filePath": "optional: the document to make current first",+    "select": "[refs] - optional: select exactly these first (everything else deselected), to show the user which parts you mean"+   },+   "longRunning": false,+   "name": "altium_selection",+   "output": {+    "kind": "board | sheet",+    "selected": "[{ref, x, y, rotation, side, footprint, widthMm, heightMm}] or [{designator, xMil, yMil, orientation, libReference, comment}]"+   },+   "pitfalls": [+    "Only components are reported (selected tracks and vias are not)."+   ],+   "related": [+    "altium_move_parts",+    "altium_replicate_layout",+    "altium_zoom_to"+   ],+   "statusVerb": null,+   "summary": "What the user has selected in the current board (components with position mm, rotation, side, footprint, size) or sheet (components with location mils and orientation).",+   "timeoutSeconds": 60+  },+  {+   "example": {+    "minImprovementMm": 0.5+   },+   "hint": "Rotate with altium_move_parts {parts:[{ref, rotation}]} (position kept).",+   "input": {+    "filePath": "optional .PcbDoc",+    "minImprovementMm": "default 0.6",+    "parts": "[refs] - optional, default every 2-pad part",+    "planeNetPads": "a net with more pads than this is treated as a plane (nearest-pad distance), default 12"+   },+   "longRunning": false,+   "name": "altium_check_orientation",+   "output": {+    "checked": "int",+    "suggestions": "[{ref, rotation, suggested, airlineNowMm, airlineThenMm, savesMm}]"+   },+   "pitfalls": [+    "Routed copper is not considered: on a routed board these are hints."+   ],+   "related": [+    "altium_move_parts",+    "altium_find_space",+    "altium_routing_state"+   ],+   "statusVerb": null,+   "summary": "Advisory: 2-pad parts whose rotation could be better. Each orthogonal rotation is scored in place by airline length (a routable net's MST with the pad there; a plane-like net's distance to its nearest pad); parts whose best rotation saves minImprovementMm are listed. Nothing is moved.",+   "timeoutSeconds": 120+  },+  {+   "example": {+    "source": [+     "U1",+     "R1",+     "C1"+    ],+    "targets": [+     [+      "U2",+      "R2",+      "C2"+     ],+     [+      "U3",+      "R3",+      "C3"+     ]+    ]+   },+   "hint": "Read the channels with altium_selection or altium_component_data; route the targets after (routing is not copied). Check with altium_find_space {check:true}.",+   "input": {+    "anchor": "optional ref from source (default the first)",+    "filePath": "optional .PcbDoc",+    "source": "[refs] of the placed channel (2 or more)",+    "targets": "[[refs], ...] - each in the same order as source"+   },+   "longRunning": false,+   "name": "altium_replicate_layout",+   "output": {+    "footprintMismatches": "[..] parts whose footprint differs from their source part",+    "moved": "int",+    "moves": "[{ref, x, y, rotation}]"+   },+   "pitfalls": [+    "Routing and pours are not copied."+   ],+   "related": [+    "altium_move_parts",+    "altium_selection",+    "altium_find_space"+   ],+   "statusVerb": null,+   "summary": "Copy one channel's placement to matching channels: each target keeps its anchor part where it is and every other part takes the source's offset and rotation from the anchor (turned by the anchors' rotation difference).",+   "timeoutSeconds": 180+  },+  {+   "example": {+    "outjobPath": "C:/proj/board.OutJob"+   },+   "hint": "Run a container with altium_run_outjob {outjobPath, projectPath, medium:'<container name>'}.",+   "input": {+    "outjobPath": "path to the .OutJob (required)"+   },+   "longRunning": false,+   "name": "altium_outjob_info",+   "output": {+    "groups": "[{name, variant, containers: [{name, type, outputs}], outputs: [{type, name, document, enabled, inMedia, settings}]}]"+   },+   "pitfalls": [+    "Reads the saved file: an OutJob open in Altium with unsaved edits is read as saved."+   ],+   "related": [+    "altium_run_outjob",+    "altium_export_manufacturing"+   ],+   "statusVerb": null,+   "summary": "An OutJob's containers (output media), the outputs in each, and their key settings (units, decimals, plotted layer count), read from the .OutJob file: Altium need not be running.",+   "timeoutSeconds": 30+  },+  {+   "example": {+    "query": "LM358"+   },+   "hint": "altium_show_symbol / altium_read_symbol on the best match; it is a style reference for a new symbol too.",+   "input": {+    "kind": "symbol (default) | footprint",+    "libraries": "[paths] - optional, default the installed libraries",+    "query": "part of the name (required)"+   },+   "longRunning": false,+   "name": "altium_find_symbol",+   "output": {+    "best": "the exact match, else the shortest name containing it",+    "matches": "[{name, library, exact}]"+   },+   "pitfalls": [+    ".IntLib files are not searched (altium_extract_intlib gives their sources); the Altium 365 workspace is searched with altium_workspace_search."+   ],+   "related": [+    "altium_show_symbol",+    "altium_read_symbol",+    "altium_list_library",+    "altium_workspace_search"+   ],+   "statusVerb": null,+   "summary": "Find symbols (or footprints with kind:'footprint') by part of their name across libraries: the given ones, or every installed .SchLib/.PcbLib.",+   "timeoutSeconds": 300+  },+  {+   "example": {+    "name": "MPN",+    "projectPath": "C:/proj/board.PrjPcb"+   },+   "hint": "Change values with altium_sheet_params; altium_bom groups the purchasing ones by MPN.",+   "input": {+    "name": "a parameter name - optional",+    "projectPath": "path to the .PrjPcb (required)"+   },+   "longRunning": false,+   "name": "altium_part_parameters",+   "output": {+    "names": "[{name, parts}]",+    "values": "[{designator, value, sheet}] (with name)"+   },+   "pitfalls": [],+   "related": [+    "altium_sheet_params",+    "altium_bom",+    "altium_schematic_data"+   ],+   "statusVerb": null,+   "summary": "The parameter names a project's parts carry (with how many parts carry each), or with name, that parameter's value on every part.",+   "timeoutSeconds": 300+  },+  {+   "example": {+    "clearance": 0.3,+    "name": "Power",+    "nets": [+     "VIN",+     "VOUT",+     "GND",+     "SW"+    ],+    "width": 0.5+   },+   "hint": "Rules land first among their kind, so they outrank the board-wide ones. Existing tracks keep their width: re-route the class's nets or check with altium_run_drc. altium_save keeps it.",+   "input": {+    "clearance": "mm - optional rule 'Adom <name> clearance' (class to All)",+    "filePath": "path to the .PcbDoc - optional, else the current board",+    "name": "class name; omit to list",+    "nets": "[string] board net names (required to make one)",+    "remove": "bool - delete the class and its two rules",+    "width": "mm (0.5) or {min, preferred, max} in mm - optional rule 'Adom <name> width'"+   },+   "longRunning": false,+   "name": "altium_net_classes",+   "output": {+    "classes": "[{name, superClass, nets}] (list)",+    "members": "int",+    "notOnBoard": "[string] nets that were not found",+    "rules": "[{kind, success, ..}]",+    "scope": "InNetClass('<name>')"+   },+   "pitfalls": [+    "Net names must exist on the board (altium_list_nets); missing ones are reported in notOnBoard, not created.",+    "A class made on the board, not in the schematic, can be removed by a later change order if the project's comparator removes board-only classes."+   ],+   "related": [+    "altium_add_rule",+    "altium_list_nets",+    "altium_read_rules",+    "altium_diff_pairs",+    "altium_set_design_rules"+   ],+   "statusVerb": null,+   "summary": "Net classes on the current board: list them with their nets, or make one (replacing a class of that name) with an optional width and clearance rule scoped to it, or remove one with its rules. Power nets wider, a high-voltage class with more clearance.",+   "timeoutSeconds": 90+  },+  {+   "example": {+    "name": "USB",+    "neg": "USB_DN",+    "pos": "USB_DP"+   },+   "hint": "Pair impedance and gap come from Altium's Differential Pairs Routing rule; set width/clearance for the pair's nets with altium_net_classes or altium_add_rule.",+   "input": {+    "filePath": "path to the .PcbDoc - optional",+    "name": "pair name; omit to list",+    "neg": "negative net (e.g. USB_N)",+    "pos": "positive net (e.g. USB_P)",+    "remove": "bool"+   },+   "longRunning": false,+   "name": "altium_diff_pairs",+   "output": {+    "pair": "string",+    "pairs": "[{name, pos, neg}] (list)",+    "replaced": "bool"+   },+   "pitfalls": [+    "Both nets must exist on the board."+   ],+   "related": [+    "altium_net_classes",+    "altium_list_nets",+    "altium_add_rule"+   ],+   "statusVerb": null,+   "summary": "Differential pairs on the current board: list them, define one from two nets (replacing a pair of that name), or remove one. Altium's differential-pair router and rules work on these.",+   "timeoutSeconds": 90+  },+  {+   "example": {+    "filePath": "C:/proj/board.PcbDoc",+    "strategy": "Default 2 Layer Board"+   },+   "hint": "Look at the result before keeping it (altium_show_2d_board) and check it with altium_routing_drc. Altium's rules drive Situs: set them first (altium_set_design_rules, altium_add_rule).",+   "input": {+    "filePath": "path to the .PcbDoc - optional, else the current board",+    "lockPreroutes": "bool, default true - existing tracks are kept",+    "ripupViolations": "bool, default false",+    "save": "bool, default false",+    "strategy": "Default 2 Layer Board | Default Multi Layer Board | Default 2 Layer With Edge Connectors | General Orthogonal | Via Miser | Cleanup (default: the dialog's current)",+    "timeout": "seconds, default 600"+   },+   "longRunning": false,+   "name": "altium_autoroute",+   "output": {+    "after": "{unconnected, errors}",+    "before": "{unconnected, errors}",+    "saved": "bool",+    "steps": "[..]"+   },+   "pitfalls": [+    "Parts outside the board outline get routed to where they sit: place them first (altium_move_parts).",+    "With lockPreroutes:false Situs rips up and reroutes existing tracks."+   ],+   "related": [+    "altium_route_net",+    "altium_routing_drc",+    "altium_set_design_rules",+    "altium_save"+   ],+   "statusVerb": null,+   "summary": "Altium's own autorouter (Situs) on the current board: picks a strategy, keeps existing routing locked by default, routes, and answers the unconnected count and DRC errors before and after. Not saved unless save:true.",+   "timeoutSeconds": 900+  },+  {+   "example": {+    "projectPath": "C:/proj/board.PrjPcb"+   },+   "hint": "Then altium_update_pcb carries the designators to the board.",+   "input": {+    "dryRun": "bool, default false",+    "projectPath": "path to the .PrjPcb (required)",+    "save": "bool, default true"+   },+   "longRunning": false,+   "name": "altium_annotate",+   "output": {+    "planned": "[..] (dryRun)",+    "renamed": "[{sheet, from, to, result}]",+    "skippedMultiPart": "[{sheet, designator, libReference, partId}]"+   },+   "pitfalls": [+    "Multi-part components with '?' are left alone (their parts share one designator); use Altium's Tools > Annotation for those."+   ],+   "related": [+    "altium_sch_place_part",+    "altium_update_pcb",+    "altium_sch_edit"+   ],+   "statusVerb": null,+   "summary": "Number every '?' designator (R?, C?, U?) across the project's sheets: the lowest free number per prefix, top-left first on each sheet. dryRun shows the plan.",+   "timeoutSeconds": 300+  },+  {+   "example": {+    "set": {+     "Revision": "B",+     "Title": "12 V to 5 V buck"+    },+    "sheet": "C:/proj/Main.SchDoc"+   },+   "hint": "The title block shows them through =Title, =DocumentNumber, =Revision ... special strings.",+   "input": {+    "save": "bool, default true",+    "set": "{name: value} (optional; missing parameters are added)",+    "sheet": "path to the .SchDoc (required)"+   },+   "longRunning": false,+   "name": "altium_doc_params",+   "output": {+    "params": "[{name, value}]",+    "saved": "bool",+    "set": "int"+   },+   "pitfalls": [],+   "related": [+    "altium_sch_edit",+    "altium_export_pdf"+   ],+   "statusVerb": null,+   "summary": "Read or set a sheet's document parameters: the title block (Title, DocumentNumber, Revision, DrawnBy, Author, CompanyName, ...).",+   "timeoutSeconds": 60+  },+  {+   "example": {+    "projectPath": "C:/proj/board.PrjPcb"+   },+   "hint": "Preview first (the default): removeParts are board parts on no sheet (test pads, fiducials, mounting holes) that the change order would DELETE; it refuses them unless allowRemovals:true. New parts land beside the board outline: place them (altium_move_parts) and check with altium_routing_drc.",+   "input": {+    "allowRemovals": "bool, default false - let the change order delete board parts that are on no sheet",+    "execute": "bool, default false - false only previews",+    "projectPath": "path to the .PrjPcb (required)",+    "timeout": "seconds, default 300"+   },+   "longRunning": false,+   "name": "altium_update_pcb",+   "output": {+    "afterExecute": "[string]",+    "board": "path",+    "changeOrder": "[string] - the dialog's first page (groups carry counts)",+    "dialogs": "[..] - anything Altium asked",+    "executed": "bool",+    "preview": "{addParts, removeParts, sheetParts, boardParts}",+    "steps": "[..]"+   },+   "pitfalls": [+    "Save the sheets first (the sch_* verbs save by default).",+    "The project must contain a .PcbDoc."+   ],+   "related": [+    "altium_annotate",+    "altium_sch_place_part",+    "altium_move_parts",+    "altium_routing_drc"+   ],+   "statusVerb": null,+   "summary": "Design > Update PCB Document from the schematic (the engineering change order): parts added, removed or renamed, nets and footprints updated on the board. Previews by default; execute:true runs it (Execute Changes, Close), refusing to delete board parts unless allowRemovals:true.",+   "timeoutSeconds": 600+  },+  {+   "example": {+    "projectPath": "C:/proj/board.PrjPcb"+   },+   "hint": "Pull the file with ab's pull_file.",+   "input": {+    "assembly": "bool, default false - Altium's Assembly Drawings output (needs a board in the project)",+    "board": "bool, default false - the composite print",+    "fabrication": "bool, default false - Altium's PCB Print fabrication drawing",+    "output": "path of the .pdf, default <project>.pdf beside it",+    "projectPath": "path to the .PrjPcb (required)",+    "timeout": "seconds, default 300"+   },+   "longRunning": false,+   "name": "altium_export_pdf",+   "output": {+    "assembly": "bool",+    "board": "bool",+    "fabrication": "bool",+    "outputBytes": "int",+    "pdfPath": "path",+    "sheets": "int"+   },+   "pitfalls": [],+   "related": [+    "altium_run_outjob",+    "altium_export_manufacturing"+   ],+   "statusVerb": null,+   "summary": "Every sheet of a project as one PDF, plus on request the board's composite print, Altium's assembly drawings (top and bottom, designators on the parts) and its fabrication print, from an OutJob in a throwaway project; the user's project is not touched.",+   "timeoutSeconds": 600+  },+  {+   "example": {+    "designators": [+     "R1",+     "R2"+    ],+    "params": {+     "MPN": "RC0402FR-0710KL",+     "Manufacturer": "YAGEO",+     "Mouser Part #": "603-RC0402FR-0710KL"+    },+    "projectPath": "C:/proj/board.PrjPcb"+   },+   "hint": "The board picks the new parameters up with Design > Update PCB Document; altium_bom reads the sheets, so it shows them at once.",+   "input": {+    "designators": "[string] or 'R1,R2' (required; every part of a multi-part component)",+    "params": "{name: value} or 'Key=Value;Key=Value' (required)",+    "projectPath": "path to the .PrjPcb: each designator is found on whichever sheet holds it",+    "save": "bool, default true",+    "sheet": "path to a .SchDoc - or",+    "timeout": "seconds, default 90"+   },+   "longRunning": false,+   "name": "altium_sheet_params",+   "output": {+    "notFound": "[designator]",+    "sheets": "[{sheet, designators, paramsSet}]",+    "updated": "[designator]"+   },+   "pitfalls": [+    "Sheet parameters, not the library: altium_set_params changes a library part."+   ],+   "related": [+    "altium_bom",+    "altium_read_schematic",+    "altium_set_params"+   ],+   "statusVerb": null,+   "summary": "Write parameters onto PLACED parts on the schematic sheets (the BOM write-back): MPN, Manufacturer, Mouser/DigiKey/LCSC numbers, anything. A missing parameter is added hidden; one undo step per sheet; each touched sheet is saved.",+   "timeoutSeconds": 300+  },+  {+   "example": {+    "outjobPath": "C:/proj/Assembly.OutJob",+    "projectPath": "C:/proj/board.PrjPcb"+   },+   "hint": "files[] names the BOM the OutJob wrote; pull it with ab's pull_file. For part data without an OutJob, altium_read_schematic returns parameters per component.",+   "input": {+    "medium": "string, default 'Folder Structure'",+    "outjobPath": "path to an .OutJob - default: the bundled example Assembly.OutJob",+    "projectPath": "path to the .PrjPcb",+    "timeout": "seconds, default 300"+   },+   "longRunning": false,+   "name": "altium_export_bom",+   "output": {+    "dialogs": "[..]",+    "elapsedSec": "float",+    "files": "[relative paths]",+    "outjob": "path",+    "outputDir": "path | null",+    "surface": "bom"+   },+   "pitfalls": [+    "Without outjobPath it exports the BOM of Altium's EXAMPLE design.",+    "The default OutJob is the example's Assembly.OutJob, which runs every output in that medium, not only a BOM."+   ],+   "related": [+    "altium_run_outjob",+    "altium_export_gerber",+    "altium_read_schematic"+   ],+   "statusVerb": null,+   "summary": "Bill of materials export: altium_run_outjob with the assembly OutJob and the 'Folder Structure' medium as defaults.",+   "timeoutSeconds": 300+  },+  {+   "example": {+    "filePath": "C:/proj/board.PcbDoc",+    "output": "C:/proj/board.step"+   },+   "hint": "outputExists and products[] are the proof; then pull the file with ab's pull_file. Save the board first: the export reads the file on disk.",+   "input": {+    "componentsOption": "int, Altium ExportComponentOptions, default 0 (all)",+    "copperOption": "int, Altium ExportCopperOption, default 0 (no copper)",+    "filePath": "path to a .PcbDoc (saved state is exported) - default: the bundled example board",+    "folded": "bool, default false",+    "holes": "bool, default true (mechanical, electrical and free pad holes)",+    "lane": "outjob | extension, default outjob",+    "modelsOption": "int, Altium ExportModelsOption, default 2 (template default)",+    "nameOccurrences": "bool, default true - name each component occurrence after its designator, as KiCad's STEP does (step2glb --molecule finds MP1-MP4 by it)",+    "output": "path - default: <board>.step",+    "singlePart": "bool, default false (one merged part)",+    "suffix": "string, required with suffixType custom",+    "suffixType": "none | board | custom | id, default none - Altium always names parts by DESIGNATOR; this only picks the suffix",+    "timeout": "seconds, default 300"+   },+   "longRunning": false,+   "name": "altium_export_step",+   "output": {+    "elapsedSec": "float",+    "lane": "string",+    "occurrences": "{named: int} - component occurrences named after their designator",+    "output": "path",+    "outputBytes": "int",+    "outputExists": "bool",+    "products": "[string] - STEP PRODUCT names (how parts are named)"+   },+   "pitfalls": [+    "Part names are designators (J1, U3...), never the Comment or footprint: Altium has no option for that (suffixType 0-3 measured).",+    "Altium leaves every assembly occurrence unnamed; the bridge names each board component's occurrence after its designator (0.2.54), so tools that find parts by occurrence name (step2glb --molecule's MP1-MP4 anchoring) work as on a KiCad STEP.",+    "The throwaway project and OutJob are closed afterwards and the temp folder removed; the user's own project is never opened or changed.",+    "pcb_not_found / step_not_written / bad_args are named; dialogs from the OutJob come back under outjob.dialogs."+   ],+   "related": [+    "altium_run_outjob",+    "altium_save",+    "altium_show_3d_board"+   ],+   "statusVerb": null,+   "summary": "Headless STEP export of a board. Default lane 'outjob': Altium's own ExportSTEP output run from a throwaway project in %TEMP% that points at the board - no dialog, no click (measured on AD 26.9.1). lane:'extension' is the older AdomBridge .NET extension path.",+   "timeoutSeconds": 360+  },+  {+   "example": {+    "filePath": "C:/lib/VL53L5CX.kicad_sym"+   },+   "hint": "Output lands in 'Imported <stem>' beside the source. Then link footprints with altium_set_params {footprint}. For Adom parts prefer altium_import_part, which needs no extension.",+   "input": {+    "filePath": "path to a .kicad_sym / .kicad_sch / .kicad_pcb (required)",+    "mode": "string, default 'Import'",+    "timeout": "seconds, default 300"+   },+   "longRunning": false,+   "name": "altium_import_kicad",+   "output": {+    "elapsedSec": "float",+    "result": "string - the extension's result file"+   },+   "pitfalls": [+    "Footprints are NOT imported by Altium's KiCad importer.",+    "Same extension caveat as altium_export_step: on AD 26.9.1 it answered extension_timeout."+   ],+   "related": [+    "altium_extension_ping",+    "altium_install_extension",+    "altium_import_part",+    "altium_set_params"+   ],+   "statusVerb": null,+   "summary": "Import a KiCad file with Altium's own KiCad importer, driven through the AdomBridge .NET extension.",+   "timeoutSeconds": 300+  },+  {+   "example": {},+   "hint": "Sequence: altium_close, altium_install_extension, altium_launch, altium_extension_ping. Only altium_export_step and altium_import_kicad need it.",+   "input": {+    "remove": "bool, default false - uninstall instead"+   },+   "longRunning": false,+   "name": "altium_install_extension",+   "output": {+    "output": "string - installer stdout tail",+    "removed": "bool",+    "stderr": "string"+   },+   "pitfalls": [+    "Refused with altium_running while any Altium is up (it holds AdomBridge.dll open); remove:true is allowed while running.",+    "A failed extension load can stop Altium starting: put extension/REMOVE-AdomBridge.cmd on the Desktop first.",+    "It was unregistered on AD 26.9.1 after coinciding with a 'Could not start Vault Explorer' warning: install only when you need those two verbs."+   ],+   "related": [+    "altium_extension_ping",+    "altium_export_step",+    "altium_import_kicad",+    "altium_close"+   ],+   "statusVerb": null,+   "summary": "Install, update or remove the AdomBridge .NET extension (the PowerShell installer shipped beside the bridge). Altium must be CLOSED.",+   "timeoutSeconds": 200+  },+  {+   "example": {},+   "hint": "success -> altium_export_step / altium_import_kicad will work. installedOnDisk:true with extension_timeout = installed but not loading on this Altium build.",+   "input": {},+   "longRunning": false,+   "name": "altium_extension_ping",+   "output": {+    "elapsedSec": "float",+    "installedOnDisk": "bool",+    "result": "string"+   },+   "pitfalls": [+    "It spawns an X2 -R helper against the running instance: do not loop it."+   ],+   "related": [+    "altium_install_extension",+    "altium_export_step",+    "altium_import_kicad"+   ],+   "statusVerb": null,+   "summary": "Does the AdomBridge .NET extension answer? Sends AdomBridge:Ping through the -R lane and waits up to 30 s for its result file.",+   "timeoutSeconds": 60+  },+  {+   "example": {+    "libraryPath": "C:/lib/Test.PcbLib",+    "name": "R0603",+    "pads": "1,-30,0,35,37,rect,0;2,30,0,35,37,rect,0"+   },+   "hint": "The footprint is registered but the library is NOT saved: altium_save {filePath: libraryPath}. Then altium_add_body for 3D and altium_show_footprint to check. For a real part use altium_build_footprint (spec with silkscreen and courtyard).",+   "input": {+    "description": "string",+    "footprints": "[{name, pads, ...}] - optional: make several in one call (each over the shared args)",+    "libraryPath": "path to a .PcbLib (required)",+    "name": "string (required)",+    "pads": "'des,x_mil,y_mil,w_mil,h_mil,rect|round,hole_mil;...' - hole 0 = SMD",+    "timeout": "seconds, default 90"+   },+   "longRunning": false,+   "name": "altium_create_footprint",+   "output": {+    "data": "{footprint, library, libraryState: opened|created, pads: int}",+    "shot": "{image: base64 png, width, height}",+    "windowEtiquette": "background"+   },+   "pitfalls": [+    "errorCode bad_number = a pad field that must be a number was not; nothing is created.",+    "Units are MILS here; altium_build_footprint specs are millimetres.",+    "A fresh library carries the stray PCBComponent_1: altium_cleanup_libs."+   ],+   "related": [+    "altium_build_footprint",+    "altium_add_body",+    "altium_save",+    "altium_show_footprint"+   ],+   "statusVerb": null,+   "summary": "Create one footprint from a pad list in a PcbLib (the library is created and saved if the path does not exist) and return a proof frame.",+   "timeoutSeconds": 90+  },+  {+   "example": {+    "libraryPath": "C:/lib/Test.SchLib",+    "name": "CH340G",+    "pins": "1,GND,left,100;2,TXD,left,200;16,VCC,right,100"+   },+   "hint": "Library is UNSAVED: altium_set_params {footprint, params}, then altium_save {filePath: libraryPath}. For multi-mode symbols use altium_build_symbol_modes.",+   "input": {+    "bodyHeightMil": "int, default 600",+    "bodyWidthMil": "int, default 800",+    "description": "string",+    "designator": "string, default 'U'",+    "libraryPath": "path to a .SchLib (required)",+    "name": "string (required)",+    "pins": "'des,name,left|right|top|bottom,offset_mil[,part[,hiddenNet]];...' - part 1..N makes a multi-part symbol (a dual op-amp: its A pins part 1, B pins part 2), 0 puts a pin on every part (power); hiddenNet hides the pin and joins it to that net by name (shared power pins: '8,V+,left,300,0,VCC', or the compiler reports duplicate pins)",+    "symbols": "[{name, pins, ...}] - optional: make several in one call (each over the shared args)",+    "timeout": "seconds, default 90"+   },+   "longRunning": false,+   "name": "altium_create_symbol",+   "output": {+    "data": "{symbol, library, libraryState: opened|created, pins: int}",+    "shot": "{image: base64 png, width, height}",+    "windowEtiquette": "background"+   },+   "pitfalls": [+    "A fresh library carries the stray Component_1: altium_cleanup_libs.",+    "Pin offsets are mils along the chosen side; check shot.image for overlapping pins."+   ],+   "related": [+    "altium_build_symbol_modes",+    "altium_set_params",+    "altium_read_symbol",+    "altium_save"+   ],+   "statusVerb": null,+   "summary": "Create one rectangular symbol from a pin list in a SchLib (the library is created and saved if the path does not exist) and return a proof frame.",+   "timeoutSeconds": 90+  },+  {+   "example": {+    "body": {+     "height": 1.75,+     "l": 4.9,+     "w": 3.9+    },+    "family": "dual",+    "libraryPath": "C:\\\\lib\\\\Parts.PcbLib",+    "name": "SOIC-8_3.9x4.9mm_P1.27mm",+    "padLength": 1.55,+    "padWidth": 0.6,+    "pins": 8,+    "pitch": 1.27,+    "span": 5.4+   },+   "hint": "dryRun first and compare padTable with the datasheet drawing; then build, check shot.image, altium_save the library and link it with altium_set_models.",+   "input": {+    "ballPad": "bga pad diameter mm",+    "body": "{w, l, height?, standoff?} mm - silkscreen, fab, courtyard, 3D",+    "cols": "bga",+    "courtyard": "margin mm, default 0.25",+    "description": "optional",+    "dryRun": "bool - return the pad table and spec only",+    "exposedPad": "{w, h, x?, y?, number?}",+    "family": "chip | dual | sot | quad | bga",+    "leftPins": "sot",+    "libraryPath": ".PcbLib (required unless dryRun; created if missing)",+    "missing": "bga ['B2', ...]",+    "name": "footprint name (required)",+    "padLength": "mm, along the lead (across the row)",+    "padShape": "roundrect (default) | rect | round | oval",+    "padWidth": "mm, along the row",+    "pins": "dual/quad total",+    "pinsPerSideX": "quad, top/bottom",+    "pinsPerSideY": "quad, left/right",+    "pitch": "mm",+    "polarized": "chip: add the pin-1 dot",+    "rightPins": "sot",+    "rows": "bga",+    "silkClearance": "default 0.2",+    "silkWidth": "default 0.15",+    "span": "mm, pad centre to pad centre across the rows (quad: left-right)",+    "spanY": "quad, top-bottom (default span)"+   },+   "longRunning": false,+   "name": "altium_footprint_from_land_pattern",+   "output": {+    "body3d": "{success}",+    "shot": "{image}",+    "spec": "the staged spec path",+    "summary": "{pads, courtyardMm, silkLines, padTable:[[name, x, y, w, h]]}"+   },+   "pitfalls": [+    "The numbers are the datasheet's RECOMMENDED LAND PATTERN, not the package outline: pad size and the pad-centre span.",+    "Pin 1 is top-left (Y up), counter-clockwise, the IPC convention."+   ],+   "related": [+    "altium_build_footprint",+    "altium_create_footprint",+    "altium_add_body",+    "altium_set_models",+    "altium_save",+    "altium_show_footprint"+   ],+   "statusVerb": null,+   "summary": "Build a complete footprint from the land pattern a datasheet prints: pads numbered the standard way (chip, dual SOIC/TSSOP/DFN, SOT, quad QFP/QFN, BGA), an exposed pad, the courtyard, silkscreen kept clear of the copper with a pin-1 dot, a fab outline with a pin-1 chamfer, and an extruded 3D body when the height is given.",+   "timeoutSeconds": 300+  },+  {+   "example": {+    "libraryPath": "C:/lib/Adom_ToF.PcbLib",+    "spec": "C:/lib/specs/VL53L5CX.fp.txt"+   },+   "hint": "Stage the spec with ab's send_files first. Then altium_add_body {step}, altium_plate_vias for thermal vias, altium_save. altium_import_part does all of it in one call.",+   "input": {+    "libraryPath": "path to a .PcbLib (required; created if missing)",+    "spec": "Windows path to the spec (tools/kicad2altium.py output; mm, Y up) (required)",+    "timeout": "seconds, default 240"+   },+   "longRunning": false,+   "name": "altium_build_footprint",+   "output": {+    "data": "{footprint, description, library, libraryState, pads, throughHolePads, drawingPrimitives}",+    "shot": "{image: base64 png, width, height}",+    "windowEtiquette": "background"+   },+   "pitfalls": [+    "The footprint name comes from the spec's META line, not from an argument.",+    "Pads with a repeated designator are legal and all map to that pin (the heatsink-via convention).",+    "Library is left UNSAVED."+   ],+   "related": [+    "altium_import_part",+    "altium_add_body",+    "altium_plate_vias",+    "altium_show_footprint"+   ],+   "statusVerb": null,+   "summary": "Build a complete footprint (pads, silkscreen, courtyard, arcs) in a PcbLib from a kicad2altium spec file on this machine, and return a proof frame.",+   "timeoutSeconds": 240+  },+  {+   "example": {+    "libraryPath": "C:/lib/Adom_ToF.SchLib",+    "spec": "C:/lib/specs/VL53L5CX.sym.txt"+   },+   "hint": "Then altium_add_symbol_art for the chip outline, altium_add_models / altium_set_models for the footprint, altium_save. altium_symbol_mode {mode} frames each alternate.",+   "input": {+    "libraryPath": "path to a .SchLib (required; created if missing)",+    "spec": "Windows path to the spec (tools/symbol_modes.py output; mils, Y up) (required)",+    "timeout": "seconds, default 240"+   },+   "longRunning": false,+   "name": "altium_build_symbol_modes",+   "output": {+    "data": "{libref, library, libraryState, modes, pins, lines, displayModeCount}",+    "shot": "{image: base64 png, width, height}",+    "windowEtiquette": "background"+   },+   "pitfalls": [+    "libref comes from the spec's META line.",+    "Compare data.displayModeCount with data.modes before trusting the alternates.",+    "Library is left UNSAVED."+   ],+   "related": [+    "altium_import_part",+    "altium_add_symbol_art",+    "altium_symbol_mode",+    "altium_add_models"+   ],+   "statusVerb": null,+   "summary": "Build ONE SchLib component with N display modes (Normal + Alternate 1..N-1) from a symbol_modes spec file on this machine, and return a proof frame.",+   "timeoutSeconds": 240+  },+  {+   "example": {+    "footprintName": "VL53L5CX",+    "libraryPath": "C:/lib/Adom_ToF.PcbLib",+    "step": "C:/lib/VL53L5CX.step"+   },+   "hint": "altium_save {filePath: libraryPath}, then altium_show_3d_chip to look at it; boards that already place the part need PCB:UpdateFootprints. Never engrave the MPN into the STEP.",+   "input": {+    "bhMil": "int, default 100 - box depth",+    "bwMil": "int, default 100 - box width",+    "footprintName": "string - required when the library holds more than one footprint",+    "hMil": "int, default 40 - box height",+    "keepExisting": "bool, default false - ADD next to the existing bodies instead of replacing them (a deliberate second body)",+    "libraryPath": "path to a .PcbLib (required)",+    "soMil": "int, default 0 - standoff",+    "step": "Windows path to a .step - optional",+    "timeout": "seconds, default 90"+   },+   "longRunning": false,+   "name": "altium_add_body",+   "output": {+    "_hint": "string - what was replaced, and a WARNING when bodies are still stacked",+    "data": "{footprint, body, replacedBodies, keptExisting, bodiesBefore, sameNameFootprints, bodiesNow, bodiesOnCanvas}",+    "normalized": "string - the shifts applied",+    "replacedBodies": "int - old bodies removed before the add",+    "shot": "{image: base64 png, width, height}"+   },+   "pitfalls": [+    "It REPLACES by default (from 0.2.52): data.replacedBodies says how many old bodies went and data.bodiesNow should read 1. keepExisting:true is the only way to stack a second body.",+    "A bad step path ('could not load model') removes nothing: the old body stays.",+    "data.sameNameFootprints > 1 = several footprints share the name and only the FIRST got the body: altium_remove_footprint the stale copies.",+    "Without footprintName it acts on the only footprint in the library (name_required when there are several).",+    "'could not load model' = the STEP path is not on THIS machine: stage it with send_files."+   ],+   "related": [+    "altium_normalize_bodies",+    "altium_show_3d_chip",+    "altium_pcb_report",+    "altium_import_part",+    "altium_footprint_primitives"+   ],+   "statusVerb": null,+   "summary": "Put a 3D body on a footprint of a PcbLib: a STEP model, or an extruded box when no step is given. It REPLACES the footprint's existing 3D bodies (a re-import never stacks a second model) unless keepExisting:true. The bridge then re-aligns bodies onto the pads (normalize_bodies) and returns a frame.",+   "timeoutSeconds": 90+  },+  {+   "example": {+    "libraryPath": "C:/lib/Adom_ToF.SchLib",+    "models": [+     {+      "description": "LGA-16 6.4x3.0",+      "name": "VL53L5CX",+      "pcblib": "C:/lib/Adom_ToF.PcbLib"+     }+    ],+    "params": {+     "Manufacturer Part Number": "VL53L5CXV0GC/1"+    },+    "symbolName": "VL53L5CX"+   },+   "hint": "Check data.models is what you sent, then altium_save. To REPLACE the footprint use altium_set_models.",+   "input": {+    "comment": "string",+    "description": "string",+    "libraryPath": "path to a .SchLib (required)",+    "models": "[{name, pcblib, description}]",+    "params": "{key: value}",+    "symbolName": "string (required)",+    "timeout": "seconds, default 120"+   },+   "longRunning": false,+   "name": "altium_add_models",+   "output": {+    "data": "{libref, models: int, params: int}",+    "shot": "{image: base64 png, width, height}"+   },+   "pitfalls": [+    "NOT idempotent: every call APPENDS another implementation. Repair = altium_set_models, or altium_remove_symbol + re-import.",+    "';' and '|' inside a description are rewritten (they are the spec separators).",+    "altium_read_symbol does not report models; use altium_symbol_prims {models:true}."+   ],+   "related": [+    "altium_set_models",+    "altium_set_params",+    "altium_symbol_prims",+    "altium_remove_symbol"+   ],+   "statusVerb": null,+   "summary": "Attach one or more PCBLIB footprint models, parameters, comment and description to a SchLib component (Altium's several-footprints-per-symbol mechanism).",+   "timeoutSeconds": 120+  },+  {+   "example": {+    "libraryPath": "C:/lib/Adom_ToF.PcbLib"+   },+   "hint": "altium_add_body already runs it. Use it on libraries built elsewhere when altium_show_footprint shows an empty canvas; then altium_save.",+   "input": {+    "footprintName": "string - optional filter",+    "libraryPath": "path to a .PcbLib - optional; opened first",+    "timeout": "seconds, default 120"+   },+   "longRunning": false,+   "name": "altium_normalize_bodies",+   "output": {+    "data": "{moved: int, shifts: string}",+    "roundtripMs": "int"+   },+   "pitfalls": [+    "Without libraryPath it acts on whatever PcbLib is current ('no PCB library is current' otherwise).",+    "It centres bodies on the pad-cluster centre: a deliberately offset body is moved too."+   ],+   "related": [+    "altium_add_body",+    "altium_pcb_report",+    "altium_show_3d_chip",+    "altium_save"+   ],+   "statusVerb": null,+   "summary": "Move every 3D body of a PcbLib's footprints (or one footprint) onto its pad cluster. A body added through the canvas lands 50000 mil from the pads in the stored frame, which makes Zoom All frame 50 inches of nothing.",+   "timeoutSeconds": 120+  },+  {+   "example": {+    "comment": "VL53L5CX",+    "libraryPath": "C:/lib/Adom_ToF.SchLib",+    "params": {+     "LCSC Part #": "C2888092",+     "Manufacturer": "ST"+    },+    "symbolName": "VL53L5CX"+   },+   "hint": "Unsaved: altium_save {filePath: libraryPath}. 'LCSC Part #' and 'Manufacturer Part Number' are the JLC/BOM keys. altium_read_symbol shows the result.",+   "input": {+    "comment": "string",+    "footprint": "string - PcbLib footprint name to link",+    "libraryPath": "path to a .SchLib (required)",+    "params": "{key: value} or 'Key=Value;Key=Value'",+    "symbolName": "string - omit for the FIRST component",+    "timeout": "seconds, default 90"+   },+   "longRunning": false,+   "name": "altium_set_params",+   "output": {+    "data": "{component, paramsSet: int, footprint}",+    "roundtripMs": "int"+   },+   "pitfalls": [+    "footprint APPENDS an implementation on every call; use altium_set_models to end up with exactly one.",+    "Parameters are created HIDDEN (visible ones pile up over the symbol): altium_set_visibility to show one.",+    "Without symbolName it edits the first component in the library."+   ],+   "related": [+    "altium_read_symbol",+    "altium_set_visibility",+    "altium_set_models",+    "altium_save"+   ],+   "statusVerb": null,+   "summary": "Set parameters on a SchLib component, optionally its comment, and optionally link a footprint by name.",+   "timeoutSeconds": 90+  },+  {+   "example": {+    "libraryPath": "C:/lib/Adom_ToF.SchLib",+    "param": "Manufacturer Part Number",+    "symbolName": "VL53L5CX",+    "visible": true+   },+   "hint": "altium_save afterwards; altium_show_symbol to see where the text landed.",+   "input": {+    "libraryPath": "path to a .SchLib - optional; opened first",+    "param": "string - the parameter name",+    "symbolName": "string",+    "timeout": "seconds, default 90",+    "visible": "bool, default true"+   },+   "longRunning": false,+   "name": "altium_set_visibility",+   "output": {+    "data": "{param, visible: '1'|'0'}",+    "roundtripMs": "int"+   },+   "pitfalls": [+    "'parameter not found' = the name must match a parameter altium_read_symbol lists.",+    "Visible parameters stack over the symbol body: show few."+   ],+   "related": [+    "altium_set_params",+    "altium_read_symbol",+    "altium_show_symbol"+   ],+   "statusVerb": null,+   "summary": "Show or hide one named parameter on a SchLib component.",+   "timeoutSeconds": 90+  },+  {+   "example": {+    "libraryPath": "C:/lib/Adom_ToF.SchLib",+    "symbolName": "VL53L5CX"+   },+   "hint": "The verification step after altium_import_part / altium_set_params: models[] shows which footprint the symbol is bound to (type PCBLIB, isCurrent true for the one a placed part uses). For the drawing use altium_symbol_prims.",+   "input": {+    "libraryPath": "path to a .SchLib - optional; opened first",+    "symbolName": "string",+    "timeout": "seconds, default 90"+   },+   "longRunning": false,+   "name": "altium_read_symbol",+   "output": {+    "data": "{libref, description, displayModeCount, pinsMode0: int, pins: [{des, name, electrical: 0 Input, 1 IO, 2 Output, 3 OpenCollector, 4 Passive, 5 HiZ, 6 OpenEmitter, 7 Power}], params: [{name, value, hidden}], models: [{type, name, isCurrent}]}",+    "roundtripMs": "int"+   },+   "pitfalls": [+    "The SCH Library panel's Model pane can lag behind altium_show_symbol; models[] here is the symbol's own record.",+    "Only mode 0 pins are listed."+   ],+   "related": [+    "altium_symbol_prims",+    "altium_set_params",+    "altium_list_library",+    "altium_show_symbol"+   ],+   "statusVerb": null,+   "summary": "Read a SchLib component back: description, display-mode count, the pins of mode 0 (designator, name, electrical type), every parameter with its hidden flag, and its model links (footprints, 3D, sim).",+   "timeoutSeconds": 90+  },+  {+   "example": {+    "from": "VL53L5CX",+    "libraryPath": "C:/lib/Adom_ToF.SchLib",+    "to": "VL53L7CX"+   },+   "hint": "Then altium_set_params on the clone (its own MPN and comment) and altium_save.",+   "input": {+    "from": "string - existing LibReference",+    "libraryPath": "path to a .SchLib - optional; opened first",+    "timeout": "seconds, default 90",+    "to": "string - new LibReference"+   },+   "longRunning": false,+   "name": "altium_clone_symbol",+   "output": {+    "data": "{from, to, primitives: int, displayModeCount}",+    "roundtripMs": "int"+   },+   "pitfalls": [+    "'target already exists' - pick another name or altium_remove_symbol first.",+    "Same library only; there is no cross-library symbol copy."+   ],+   "related": [+    "altium_rename_symbol",+    "altium_remove_symbol",+    "altium_set_params",+    "altium_save"+   ],+   "statusVerb": null,+   "summary": "Copy a symbol inside the current SchLib under a new name, all display modes included.",+   "timeoutSeconds": 90+  },+  {+   "example": {+    "from": "Component_1",+    "libraryPath": "C:/lib/Adom_ToF.SchLib",+    "to": "VL53L5CX"+   },+   "hint": "altium_save, then altium_list_library to confirm.",+   "input": {+    "from": "string",+    "libraryPath": "path to a .SchLib - optional; opened first",+    "timeout": "seconds, default 90",+    "to": "string"+   },+   "longRunning": false,+   "name": "altium_rename_symbol",+   "output": {+    "data": "{renamed: string}",+    "roundtripMs": "int"+   },+   "pitfalls": [+    "'to' is NOT checked: an empty or already-used name is applied as given. altium_list_library first."+   ],+   "related": [+    "altium_clone_symbol",+    "altium_rename_footprint",+    "altium_list_library",+    "altium_save"+   ],+   "statusVerb": null,+   "summary": "Rename a component (LibReference) in the current SchLib.",+   "timeoutSeconds": 90+  },+  {+   "example": {+    "from": "PCBComponent_1",+    "libraryPath": "C:/lib/Adom_ToF.PcbLib",+    "to": "VL53L5CX"+   },+   "hint": "altium_save, then re-link symbols that referenced the old name: altium_set_models {footprint: <new name>}.",+   "input": {+    "from": "string",+    "libraryPath": "path to a .PcbLib - optional; opened first",+    "timeout": "seconds, default 90",+    "to": "string"+   },+   "longRunning": false,+   "name": "altium_rename_footprint",+   "output": {+    "data": "{renamed: string}",+    "roundtripMs": "int"+   },+   "pitfalls": [+    "'to' is NOT checked: an empty or already-used name is applied as given.",+    "Symbols link footprints BY NAME: a rename silently breaks their models."+   ],+   "related": [+    "altium_copy_footprint",+    "altium_rename_symbol",+    "altium_set_models",+    "altium_save"+   ],+   "statusVerb": null,+   "summary": "Rename a footprint in the current PcbLib.",+   "timeoutSeconds": 90+  },+  {+   "example": {+    "libraryPath": "C:/lib/Adom_ToF.SchLib",+    "symbolName": "VL53L5CX"+   },+   "hint": "The repair step for a symbol with duplicate models: remove, altium_import_part again, altium_save.",+   "input": {+    "libraryPath": "path to a .SchLib - optional; opened first",+    "symbolName": "string",+    "timeout": "seconds, default 60"+   },+   "longRunning": false,+   "name": "altium_remove_symbol",+   "output": {+    "data": "{removed: string}",+    "roundtripMs": "int"+   },+   "pitfalls": [+    "No undo through the bridge; the library is unsaved until altium_save, so altium_close_document without saving is the way back."+   ],+   "related": [+    "altium_import_part",+    "altium_cleanup_libs",+    "altium_list_library",+    "altium_save"+   ],+   "statusVerb": null,+   "summary": "Delete one component from the current SchLib.",+   "timeoutSeconds": 60+  },+  {+   "example": {+    "footprintName": "VL53L5CX",+    "libraryPath": "C:/lib/Adom_ToF.PcbLib"+   },+   "hint": "altium_save afterwards. Symbols that linked it keep a dangling model: altium_set_models.",+   "input": {+    "footprintName": "string",+    "libraryPath": "path to a .PcbLib - optional; opened first",+    "timeout": "seconds, default 90"+   },+   "longRunning": false,+   "name": "altium_remove_footprint",+   "output": {+    "data": "{removed: string}",+    "roundtripMs": "int"+   },+   "pitfalls": [+    "No undo through the bridge; closing the library without saving is the way back."+   ],+   "related": [+    "altium_remove_symbol",+    "altium_cleanup_libs",+    "altium_list_library",+    "altium_save"+   ],+   "statusVerb": null,+   "summary": "Delete one footprint from the current PcbLib.",+   "timeoutSeconds": 90+  },+  {+   "example": {+    "from": "VL53L5CX",+    "libraryPath": "C:/lib/Adom_ToF.PcbLib",+    "to": "VL53L5CX_HANDSOLDER"+   },+   "hint": "For a footprint from ANOTHER library use altium_xcopy_footprint. Then altium_save.",+   "input": {+    "from": "string",+    "libraryPath": "path to a .PcbLib - optional; opened first",+    "timeout": "seconds, default 90",+    "to": "string"+   },+   "longRunning": false,+   "name": "altium_copy_footprint",+   "output": {+    "data": "{from, to, primitives: int}",+    "roundtripMs": "int"+   },+   "pitfalls": [+    "'target already exists' - choose another name or altium_remove_footprint first."+   ],+   "related": [+    "altium_xcopy_footprint",+    "altium_rename_footprint",+    "altium_pcb_report"+   ],+   "statusVerb": null,+   "summary": "Duplicate a footprint inside the current PcbLib under a new name (every primitive replicated).",+   "timeoutSeconds": 90+  },+  {+   "example": {+    "from": "LGA-16",+    "fromLib": "C:/lib/Vendor.PcbLib",+    "to": "VL53L5CX",+    "toLib": "C:/lib/Adom_ToF.PcbLib"+   },+   "hint": "altium_pcb_report {path: toLib, name} to confirm pads and bodies arrived, then altium_save {filePath: toLib}.",+   "input": {+    "from": "string - footprint name in the source",+    "fromLib": "path to the source .PcbLib",+    "timeout": "seconds, default 120",+    "to": "string - default: same as from",+    "toLib": "path to the destination .PcbLib - optional; else the current PcbLib"+   },+   "longRunning": false,+   "name": "altium_xcopy_footprint",+   "output": {+    "data": "{to, primitives: int}",+    "roundtripMs": "int"+   },+   "pitfalls": [+    "'open the destination PcbLib first' = pass toLib.",+    "'target already exists' in the destination: pass a different to."+   ],+   "related": [+    "altium_copy_footprint",+    "altium_pcb_report",+    "altium_save"+   ],+   "statusVerb": null,+   "summary": "Copy a footprint ACROSS libraries: from fromLib into toLib (opened and made current), optionally under a new name.",+   "timeoutSeconds": 120+  },+  {+   "example": {+    "assigns": "1=GND;2=VDD",+    "footprintName": "VL53L5CX",+    "libraryPath": "C:/lib/Adom_ToF.PcbLib",+    "pcbPath": "C:/proj/tof.PcbDoc"+   },+   "hint": "Built for single-part test boards (altium_test_board): iterate on a footprint and re-prove with altium_show_3d_board. altium_save the board afterwards.",+   "input": {+    "assigns": "'padDes=NET;...'",+    "footprintName": "string",+    "libraryPath": "path to the .PcbLib holding the new footprint",+    "pcbPath": "path to the .PcbDoc - optional; opened first",+    "timeout": "seconds, default 240"+   },+   "longRunning": false,+   "name": "altium_swap_footprint",+   "output": {+    "data": "{placed, padsOnComp, bodiesOnComp}",+    "windowEtiquette": "background"+   },+   "pitfalls": [+    "There is NO designator argument: on a multi-part board it replaces whichever component the iterator returns first.",+    "Nets come ONLY from assigns (padDes=NET, all pads sharing a designator get the net): pads you do not list end up with no net."+   ],+   "related": [+    "altium_test_board",+    "altium_show_3d_board",+    "altium_footprint_primitives"+   ],+   "statusVerb": null,+   "summary": "Replace the FIRST component on the current board with a footprint from a PcbLib at the same position, re-assigning nets by pad designator. Tracks are untouched, so the wiring survives.",+   "timeoutSeconds": 240+  },+  {+   "example": {+    "libraryPath": "C:/lib/Adom_ToF.SchLib",+    "mode": 1+   },+   "hint": "Select the component first with altium_show_symbol {libraryPath, symbolName}, then one call per mode up to displayModeCount-1.",+   "input": {+    "libraryPath": "path to a .SchLib - optional; opened first",+    "mode": "int, default 0",+    "timeout": "seconds, default 60"+   },+   "longRunning": false,+   "name": "altium_symbol_mode",+   "output": {+    "data": "{mode, displayModeCount, libref}",+    "shot": "{image: base64 png, width, height}",+    "windowEtiquette": "background"+   },+   "pitfalls": [+    "There is no symbolName argument: it acts on the component current in the editor.",+    "Leave it on mode 0 when done: the editor keeps showing the last mode to the user."+   ],+   "related": [+    "altium_show_symbol",+    "altium_build_symbol_modes",+    "altium_read_symbol"+   ],+   "statusVerb": null,+   "summary": "Show one display mode (0 = Normal, 1.. = alternates) of the CURRENT SchLib component in the editor and capture a frame. Drives the editor's own mode toggle, because setting DisplayMode alone does not repaint.",+   "timeoutSeconds": 60+  },+  {+   "example": {+    "libraryPath": "C:/lib/Adom_ToF.SchLib"+   },+   "hint": "Run once per new library after the first import, then altium_save and altium_list_library.",+   "input": {+    "libraryPath": "path to a .SchLib/.PcbLib - optional; opened first",+    "timeout": "seconds, default 90"+   },+   "longRunning": false,+   "name": "altium_cleanup_libs",+   "output": {+    "data": "{removed: int}",+    "roundtripMs": "int"+   },+   "pitfalls": [+    "The SchLib side removes ANY component named Component_1 without checking that it is empty.",+    "removed:0 is normal on a library that was already clean."+   ],+   "related": [+    "altium_list_library",+    "altium_remove_symbol",+    "altium_remove_footprint",+    "altium_save"+   ],+   "statusVerb": null,+   "summary": "Remove the factory-default stray every fresh library carries: Component_1 from the current SchLib and an empty (no pads) PCBComponent_1 from the current PcbLib.",+   "timeoutSeconds": 90+  },+  {+   "example": {+    "libraryPath": "C:/lib/Adom_ToF.SchLib"+   },+   "hint": "Takes effect live, no restart. For a wiki bundle zip use altium_install_library_bundle (unpack + register + proof).",+   "input": {+    "libraryPath": "path to a .IntLib/.SchLib/.PcbLib/.DbLib (required)",+    "timeout": "seconds, default 90"+   },+   "longRunning": false,+   "name": "altium_install_library",+   "output": {+    "data": "{installed: path}",+    "roundtripMs": "int"+   },+   "pitfalls": [+    "'library not found' = the path must exist on THIS machine.",+    "Registers the file where it is: moving or deleting it later leaves a dead entry."+   ],+   "related": [+    "altium_install_library_bundle",+    "altium_list_library",+    "altium_show_library"+   ],+   "statusVerb": null,+   "summary": "Register a library file in Altium's Installed Libraries (IntegratedLibraryManager.InstallLibrary) so its parts appear in the Components panel.",+   "timeoutSeconds": 150+  },+  {+   "example": {+    "libpkgPath": "C:/lib/Adom_ToF.LibPkg",+    "output": "C:/lib/out"+   },+   "hint": "Then altium_install_library {libraryPath: <the .IntLib>}. Check the file exists: the reply does not verify it was written.",+   "input": {+    "libpkgPath": "path to a .LibPkg (required)",+    "output": "destination directory",+    "timeout": "seconds, default 300"+   },+   "longRunning": false,+   "name": "altium_compile_intlib",+   "output": {+    "data": "{libpkg, outputDir}",+    "roundtripMs": "int"+   },+   "pitfalls": [+    "The LibPkg is opened as a project in the user's workspace and stays open.",+    "success only means the compile was dispatched; verify the .IntLib on disk."+   ],+   "related": [+    "altium_extract_intlib",+    "altium_install_library"+   ],+   "statusVerb": null,+   "summary": "Compile a .LibPkg into an integrated library (.IntLib).",+   "timeoutSeconds": 300+  },+  {+   "example": {+    "intlibPath": "C:/lib/Vendor.IntLib"+   },+   "hint": "Then altium_list_library / altium_read_symbol on the extracted sources.",+   "input": {+    "intlibPath": "path to an .IntLib (required)",+    "timeout": "seconds, default 300"+   },+   "longRunning": false,+   "name": "altium_extract_intlib",+   "output": {+    "data": "{intlib, sources: [path]}",+    "roundtripMs": "int"+   },+   "pitfalls": [+    "sources[] lists EVERY .SchLib/.PcbLib in any open project, not only the ones this call extracted."+   ],+   "related": [+    "altium_compile_intlib",+    "altium_list_library",+    "altium_install_library"+   ],+   "statusVerb": null,+   "summary": "Extract the editable .SchLib/.PcbLib sources from a compiled .IntLib (Altium writes them next to the IntLib and opens them).",+   "timeoutSeconds": 300+  },+  {+   "example": {+    "footprintName": "VL53L5CX",+    "libraryPath": "C:/lib/Adom_ToF.PcbLib",+    "pcbPath": "C:/proj/one.PcbDoc"+   },+   "hint": "Prefer altium_test_board (outline, several parts, nets). bodiesOnComp:0 with libBodies>0 means the body did not copy: report it.",+   "input": {+    "footprintName": "string",+    "libraryPath": "path to a .PcbLib (required)",+    "pcbPath": "path to save the new .PcbDoc - optional",+    "timeout": "seconds, default 90"+   },+   "longRunning": false,+   "name": "altium_place_on_pcb",+   "output": {+    "data": "{pcb, placed, libFile, libBodies, primitives, padsOnComp, bodiesOnComp}",+    "shot": "{image: base64 png, width, height}",+    "windowEtiquette": "background"+   },+   "pitfalls": [+    "Every call creates a NEW PcbDoc; without pcbPath it exists only unsaved.",+    "'footprint not found in PcbLib' - names from altium_list_library."+   ],+   "related": [+    "altium_test_board",+    "altium_place_on_sch",+    "altium_show_3d_board"+   ],+   "statusVerb": null,+   "summary": "Place ONE footprint from a PcbLib on a new board and return a proof frame. The single-part predecessor of altium_test_board.",+   "timeoutSeconds": 90+  },+  {+   "example": {},+   "hint": "Read shot.image for the product row (Trial/Full, days left, seat n/n): that lives ONLY on the page. Not licensed -> altium_ensure_licensed.",+   "input": {},+   "longRunning": false,+   "name": "altium_license_status",+   "output": {+    "licenceState": "string",+    "licensed": "bool | null",+    "running": "bool",+    "seatStuck": "'possible' | null",+    "shot": "{image: base64 png, ...}",+    "summary": "string"+   },+   "pitfalls": [+    "It brings up the License Management page inside Altium (when the agent is resident), replacing the view the user had.",+    "One On-Demand seat serves every Altium on the account: 'no seats available' usually means ANOTHER running Altium holds it - altium_close that one."+   ],+   "related": [+    "altium_ensure_licensed",+    "altium_license",+    "altium_instances"+   ],+   "statusVerb": null,+   "summary": "Plain-language answer to 'what Altium licence am I on?': the state from the window title plus a capture of the License Management page.",+   "timeoutSeconds": 60+  },+  {+   "example": {},+   "hint": "success -> drive. errorCode no_seat_available: find the Altium holding the seat (altium_instances here, or another machine) and altium_close it, then call again. errorCode no_licence_on_account: the account has only the Viewer; ask the user (admin assigns a licence, trial, or the other Altium build). errorCode no_author_seat (Develop): every workspace Author seat is taken; ask the user.",+   "input": {+    "allowSignInReset": "bool - if no seat is free, Sign Out (the human must sign back in)",+    "allowTrial": "bool - if no seat is free, open the trial flow in the browser",+    "launchWaitSec": "int, default 240"+   },+   "longRunning": false,+   "name": "altium_ensure_licensed",+   "output": {+    "alreadyLicensed": "bool",+    "licenceState": "string",+    "needsHuman": "string - only for browser sign-in / trial activation / a licence the account lacks",+    "page": "{notUsingLicence, requestAccessBanner, viewerOnly} read off the licence page",+    "seatStuck": "bool",+    "shot": "{..}",+    "steps": "[..]",+    "summary": "string"+   },+   "pitfalls": [+    "One On-Demand seat serves EVERY Altium on the account. A KILLED Altium strands the seat (page shows Seats 0/1 while every claim fails): altium_close releases it, waiting does not.",+    "allowSignInReset:true signs the user OUT and ends with needsHuman (browser sign-in). Use it only when nothing else holds the seat.",+    "It may launch Altium, which raises Altium over the user's work once."+   ],+   "related": [+    "altium_license",+    "altium_license_status",+    "altium_instances",+    "altium_close"+   ],+   "statusVerb": null,+   "summary": "One call to get a usable Altium: launch if needed, then claim a seat (Use License via UIA). Optional escalations: sign out to force a seat release, or start the free-trial flow.",+   "timeoutSeconds": 300+  },+  {+   "example": {},+   "hint": "Follow nextAction and call again until stage is done. Not installed -> altium_install (the staged install verb; its licence stage does the clicks itself).",+   "input": {},+   "longRunning": false,+   "name": "altium_setup_trial",+   "output": {+    "licenceState": "string",+    "needsHuman": "string - when a person must act",+    "nextAction": "string",+    "stage": "install | launch | signin | done",+    "summary": "string"+   },+   "pitfalls": [+    "Signed in but unlicensed, it starts the free-trial flow in the BROWSER and answers trial_needs_browser: a person names the workspace, accepts terms and clicks Activate.",+    "stage launch answers success:true without launching anything: call altium_launch yourself."+   ],+   "related": [+    "altium_install",+    "altium_ensure_licensed",+    "altium_signin_status",+    "altium_launch"+   ],+   "statusVerb": null,+   "summary": "Onboarding state machine: from nothing to a working trial. Each call reports the stage it found (install | launch | signin | done) and the ONE next action; human steps are named, not faked.",+   "timeoutSeconds": 300+  },+  {+   "example": {},+   "hint": "To see WHO is signed in: altium_license {action:'show'} and read the page ('Sign Out' visible = already signed in). Not connected to a workspace -> altium_connect_workspace.",+   "input": {},+   "longRunning": false,+   "name": "altium_signin",+   "output": {+    "currentUserProcess": "object - only when the agent is resident",+    "licenceState": "string",+    "licensed": "bool | null",+    "running": "bool",+    "workspaceConnected": "bool",+    "workspaceMenu": "string"+   },+   "pitfalls": [+    "Despite the name there are no credential arguments and nothing is typed or clicked.",+    "Never ask the user for their Altium password: the browser flow is theirs."+   ],+   "related": [+    "altium_signin_status",+    "altium_license",+    "altium_connect_workspace",+    "altium_install"+   ],+   "statusVerb": null,+   "summary": "Report-only alias of altium_signin_status: it does NOT sign anyone in. Signing in to Altium is a browser flow only the human can complete.",+   "timeoutSeconds": 300+  },+  {+   "example": {},+   "hint": "workspaceConnected:false -> altium_connect_workspace opens the picker. The signed-in account name is on the License Management page: altium_license {action:'show'}.",+   "input": {},+   "longRunning": false,+   "name": "altium_signin_status",+   "output": {+    "currentUserProcess": "object - only when the agent is resident",+    "licenceState": "string",+    "licensed": "bool | null",+    "running": "bool",+    "workspace": "null - the name is not read",+    "workspaceConnected": "bool",+    "workspaceMenu": "string"+   },+   "pitfalls": [+    "workspaceConnected is inferred from the ABSENCE of a 'Not Connected' menu item: a UIA miss reads as connected."+   ],+   "related": [+    "altium_connect_workspace",+    "altium_license",+    "altium_license_status"+   ],+   "statusVerb": null,+   "summary": "Account and Altium 365 workspace connection as Altium's own UI reports it: licence state from the title, and whether the workspace menu reads 'Not Connected'.",+   "timeoutSeconds": 60+  },+  {+   "example": {},+   "hint": "Click the wanted row with ab's desktop_click {space:'image', shotId, x, y} straight away, then altium_signin_status to confirm.",+   "input": {},+   "longRunning": false,+   "name": "altium_connect_workspace",+   "output": {+    "picker": "{hwnd, title, className: TWorkspacesForm, rect}",+    "shot": "ab desktop_screenshot_window reply: image + coordMap/shotId",+    "windowEtiquette": "background"+   },+   "pitfalls": [+    "picker_not_open usually means Altium is ALREADY connected (the menu shows the workspace name).",+    "The picker is a popup: it closes on focus loss, so click before doing anything else.",+    "A workspace with no free Author seat connects view-only: new documents then fail with view_only_no_author_seat."+   ],+   "related": [+    "altium_signin_status",+    "altium_a365_status",+    "altium_license"+   ],+   "statusVerb": null,+   "summary": "Open Altium's workspace picker (the 'Not Connected' menu) and return a screenshot with a coordMap. The list is a custom control with no UIA children, so the caller clicks the row.",+   "timeoutSeconds": 60+  },+  {+   "example": {+    "workspace": "acme.365.altium.com"+   },+   "hint": "Then altium_a365_status to prove the token is live. Preferred over a manual token: altium_a365_oauth_begin {clientId, workspace}.",+   "input": {+    "token": "string - optional developer token",+    "workspace": "string - e.g. acme.365.altium.com (required unless already stored)"+   },+   "longRunning": false,+   "name": "altium_a365_configure",+   "output": {+    "hasToken": "bool",+    "workspace": "string"+   },+   "pitfalls": [+    "The token is written to a365.json in the bridge cache: altium_uninstall deletes it, altium_a365_logout removes it.",+    "Never echo a token back to the user or into a log."+   ],+   "related": [+    "altium_a365_status",+    "altium_a365_oauth_begin",+    "altium_a365_query"+   ],+   "statusVerb": null,+   "summary": "Store the Altium 365 workspace domain and, optionally, a Bearer token created by hand in the workspace web UI (Admin - Developer). Local-only; tokens never appear in replies.",+   "timeoutSeconds": 15+  },+  {+   "example": {+    "clientId": "<your Altium-issued client id>",+    "workspace": "acme.365.altium.com"+   },+   "hint": "Open authorizeUrl in the user's EXISTING browser only after this returns, then poll altium_a365_oauth_status until status is authorized.",+   "input": {+    "clientId": "string - an Altium-issued public OAuth client id (or the stored one)",+    "selectWorkspace": "strict (default) | optional | none",+    "timeout": "int seconds, default 300 - how long to wait for the consent",+    "workspace": "string - e.g. acme.365.altium.com (or the stored one)"+   },+   "longRunning": false,+   "name": "altium_a365_oauth_begin",+   "output": {+    "authorizeUrl": "string",+    "status": "waiting",+    "workspace": "string"+   },+   "pitfalls": [+    "a365_client_id_required: OAuth clients come from Altium's Developer Center enrollment. Never reuse a first-party client id.",+    "a365_oauth_in_progress: one flow at a time - altium_a365_oauth_cancel the old one first.",+    "The consent page is the human's step; tokens never appear in any reply."+   ],+   "related": [+    "altium_a365_oauth_status",+    "altium_a365_oauth_cancel",+    "altium_a365_status"+   ],+   "statusVerb": null,+   "summary": "Start the Altium 365 OAuth (PKCE + ActionWait) sign-in: the bridge starts listening FIRST, then returns the authorize URL for the user's browser. Works with Altium closed.",+   "timeoutSeconds": 30+  },+  {+   "example": {},+   "hint": "Poll every few seconds while waiting. authorized -> altium_a365_library_overview proves the workspace-scoped token. error -> read errorCode, then altium_a365_oauth_begin again.",+   "input": {},+   "longRunning": false,+   "name": "altium_a365_oauth_status",+   "output": {+    "claims": "object - unverified token claim summary (authorized)",+    "error": "string (error)",+    "errorCode": "string (error)",+    "status": "idle | waiting | authorized | cancelled | error",+    "workspace": "string"+   },+   "pitfalls": [+    "success is false only for status error; waiting and idle are success:true.",+    "State lives in bridge memory: a bridge restart mid-flow reads idle."+   ],+   "related": [+    "altium_a365_oauth_begin",+    "altium_a365_oauth_cancel",+    "altium_a365_library_overview"+   ],+   "statusVerb": null,+   "summary": "State of the OAuth flow started by altium_a365_oauth_begin: idle | waiting | authorized | cancelled | error.",+   "timeoutSeconds": 10+  },+  {+   "example": {},+   "hint": "Poll altium_a365_oauth_status until it reads cancelled, then altium_a365_oauth_begin again if wanted.",+   "input": {},+   "longRunning": false,+   "name": "altium_a365_oauth_cancel",+   "output": {+    "alreadyStopped": "bool",+    "status": "cancelling | <current status>"+   },+   "pitfalls": [+    "The worker notices the cancel between long-polls (up to about 75 s)."+   ],+   "related": [+    "altium_a365_oauth_begin",+    "altium_a365_oauth_status"+   ],+   "statusVerb": null,+   "summary": "Cancel a waiting OAuth flow.",+   "timeoutSeconds": 10+  },+  {+   "example": {},+   "hint": "Call when altium_a365_query answers a365_graphql_http 401 or altium_a365_status shows tokenLive:false, then retry.",+   "input": {},+   "longRunning": false,+   "name": "altium_a365_refresh",+   "output": {+    "claims": "object - unverified token claim summary",+    "workspace": "string"+   },+   "pitfalls": [+    "a365_refresh_not_configured = no OAuth grant stored (a manual developer token cannot be refreshed).",+    "a365_refresh_failed usually means the grant was revoked: altium_a365_oauth_begin again."+   ],+   "related": [+    "altium_a365_status",+    "altium_a365_query",+    "altium_a365_oauth_begin"+   ],+   "statusVerb": null,+   "summary": "Exchange the stored refresh token for a new access token (and store a rotated refresh token if one comes back).",+   "timeoutSeconds": 60+  },+  {+   "example": {},+   "hint": "altium_a365_status afterwards should read hasToken:false. Sign in again with altium_a365_oauth_begin.",+   "input": {},+   "longRunning": false,+   "name": "altium_a365_logout",+   "output": {+    "revoked": "bool - a remote revocation happened",+    "workspace": "string"+   },+   "pitfalls": [+    "If the remote revocation fails (a365_revoke_failed / a365_network_error) the LOCAL tokens are kept on purpose: retry.",+    "This is the Altium 365 API grant only; it does not sign Altium Designer out or release a licence seat."+   ],+   "related": [+    "altium_a365_status",+    "altium_a365_oauth_begin",+    "altium_uninstall"+   ],+   "statusVerb": null,+   "summary": "Revoke the refresh grant at Altium and remove every cached OAuth token. Workspace and client id stay configured.",+   "timeoutSeconds": 60+  },+  {+   "example": {},+   "hint": "hasToken:false -> altium_a365_oauth_begin (or altium_a365_configure {token}). tokenLive:false with hasRefreshToken -> altium_a365_refresh. Live -> altium_a365_library_overview / altium_a365_query.",+   "input": {},+   "longRunning": false,+   "name": "altium_a365_status",+   "output": {+    "clientConfigured": "bool",+    "configured": "bool",+    "endpoint": "string - the GraphQL URL",+    "hasRefreshToken": "bool",+    "hasToken": "bool",+    "probe": "object - probe data or error",+    "tokenLive": "bool",+    "workspace": "string | null"+   },+   "pitfalls": [+    "Independent of Altium Designer: this lane works while X2.exe is closed and says nothing about the desktop licence seat."+   ],+   "related": [+    "altium_a365_oauth_begin",+    "altium_a365_refresh",+    "altium_a365_query",+    "altium_a365_configure"+   ],+   "statusVerb": null,+   "summary": "Is the Altium 365 lane configured, is there a token, and is it LIVE (probed with a one-row GraphQL query)?",+   "timeoutSeconds": 60+  },+  {+   "example": {+    "query": "{ desProjects(first: 10) { nodes { name } } }"+   },+   "hint": "Discover fields first with altium_a365_library_schema (or https://<workspace>/api/voyager). a365_graphql_http / a365_network_error -> altium_a365_status, then altium_a365_refresh.",+   "input": {+    "query": "string - GraphQL (required)",+    "timeout": "int seconds, default 60",+    "variables": "object"+   },+   "longRunning": false,+   "name": "altium_a365_query",+   "output": {+    "data": "object | null",+    "errors": "[..] | null"+   },+   "pitfalls": [+    "success is false whenever the reply carries errors[], even with partial data.",+    "Mutations change the user's shared workspace for everyone: know the rollback/delete mutation before running one.",+    "a365_not_configured = no workspace or token stored."+   ],+   "related": [+    "altium_a365_library_schema",+    "altium_a365_library_overview",+    "altium_a365_refresh",+    "altium_a365_files_upload"+   ],+   "statusVerb": null,+   "summary": "Run one GraphQL query or mutation against the workspace's GraphQL gateway (https://<workspace>/svc/napi/gateway/graphql/) with the stored token.",+   "timeoutSeconds": 60+  },+  {+   "example": {+    "filePath": "C:/lib/Adom_ToF.SchLib"+   },+   "hint": "Pass fileId straight into the matching mutation via altium_a365_query {query, variables}. Do not parse or persist it.",+   "input": {+    "filePath": "path on THIS machine (required)",+    "maxBytes": "int, default 104857600 (100 MB)",+    "timeout": "int seconds, default 120"+   },+   "longRunning": false,+   "name": "altium_a365_files_upload",+   "output": {+    "expiresAfterHoursInactive": "24",+    "fileId": "string - opaque"+   },+   "pitfalls": [+    "a365_file_not_found: the path must be on the bridge's machine - stage it with send_files.",+    "The id expires after 24 h of inactivity."+   ],+   "related": [+    "altium_a365_query",+    "altium_a365_library_schema"+   ],+   "statusVerb": null,+   "summary": "Upload one local file to the workspace's Files Service and get a temporary opaque file id to pass to a library mutation.",+   "timeoutSeconds": 120+  },+  {+   "example": {},+   "hint": "The proof that a fresh OAuth token is workspace-scoped. Then altium_a365_library_schema before attempting any mutation.",+   "input": {},+   "longRunning": false,+   "name": "altium_a365_library_overview",+   "output": {+    "data": "{desLibrary: {name, description, updatedAt, components: {nodes: [{id, name}]}, footprints: {totalCount}, symbols: {totalCount}}}",+    "errors": "[..] | null"+   },+   "pitfalls": [+    "Same error codes as altium_a365_query (a365_not_configured, a365_graphql_http, a365_network_error)."+   ],+   "related": [+    "altium_a365_status",+    "altium_a365_library_schema",+    "altium_a365_query"+   ],+   "statusVerb": null,+   "summary": "One fixed read-only query: the workspace library's name, description, update time, component ids/names and footprint/symbol totals.",+   "timeoutSeconds": 60+  },+  {+   "example": {},+   "hint": "Pick the mutation, understand its input types AND its rollback/delete counterpart, upload inputs with altium_a365_files_upload, then altium_a365_query.",+   "input": {+    "timeout": "int seconds, default 60"+   },+   "longRunning": false,+   "name": "altium_a365_library_schema",+   "output": {+    "mutations": "[{name, description, args: [{name, type}]}]",+    "queries": "[{name, description}]"+   },+   "pitfalls": [+    "A name filter over introspection: a relevant field named differently is not listed - the full schema is at https://<workspace>/api/voyager."+   ],+   "related": [+    "altium_a365_query",+    "altium_a365_files_upload",+    "altium_a365_library_overview"+   ],+   "statusVerb": null,+   "summary": "Read-only schema map: the GraphQL queries and mutations whose names mention lib, component, symbol, footprint, part or datasheet, with mutation argument types.",+   "timeoutSeconds": 60+  }+ ]+}\ No newline at end of file
reference/fusion360-1.11.44.jsonadded+182
@@ -0,0 +1,182 @@+{+ "bridge": "fusion360",+ "version": "1.11.44",+ "verbs": [+  "fusion_account",+  "fusion_action_ledger",+  "fusion_activate_document",+  "fusion_addin_status",+  "fusion_alerts",+  "fusion_apply_fusion_update",+  "fusion_apply_pours",+  "fusion_aps_browse",+  "fusion_aps_create_folder",+  "fusion_aps_download",+  "fusion_aps_file_info",+  "fusion_aps_forget_browser",+  "fusion_aps_get",+  "fusion_aps_get_browser",+  "fusion_aps_open",+  "fusion_aps_recent",+  "fusion_aps_search",+  "fusion_aps_set_browser",+  "fusion_aps_set_client_id",+  "fusion_aps_signin",+  "fusion_aps_status",+  "fusion_aps_upload",+  "fusion_aps_versions",+  "fusion_assembly_bom",+  "fusion_attach_3d_package",+  "fusion_bind_library_3d",+  "fusion_board_bom",+  "fusion_board_info",+  "fusion_board_seed",+  "fusion_board_source",+  "fusion_board_stackup",+  "fusion_build_library_3d",+  "fusion_cancel_run",+  "fusion_capture_library_views",+  "fusion_change_pours",+  "fusion_check_recovery",+  "fusion_claim_seat",+  "fusion_cleanup_cloud_files",+  "fusion_close_all_documents",+  "fusion_close_document",+  "fusion_close_window",+  "fusion_create_cloud_folder",+  "fusion_create_library",+  "fusion_delete_cloud_file",+  "fusion_demo",+  "fusion_demo_progress",+  "fusion_describe",+  "fusion_detect_layers",+  "fusion_dismiss_blocking_dialogs",+  "fusion_document_info",+  "fusion_eagle_import_status",+  "fusion_electron_pan",+  "fusion_electron_run",+  "fusion_electron_select",+  "fusion_electron_zoom",+  "fusion_electronics_query",+  "fusion_execute_text_command",+  "fusion_export_3mf",+  "fusion_export_board_image",+  "fusion_export_bom",+  "fusion_export_cloud_file",+  "fusion_export_cpl",+  "fusion_export_dwg",+  "fusion_export_dxf",+  "fusion_export_eagle_source",+  "fusion_export_f3d",+  "fusion_export_fbx",+  "fusion_export_gerbers",+  "fusion_export_iges",+  "fusion_export_lbr",+  "fusion_export_obj",+  "fusion_export_optimized_glb",+  "fusion_export_sat",+  "fusion_export_skp",+  "fusion_export_source",+  "fusion_export_step",+  "fusion_export_stl",+  "fusion_export_usdz",+  "fusion_fetch_optimized_glb",+  "fusion_generate_package",+  "fusion_get_app_state",+  "fusion_get_design_info",+  "fusion_get_parameters",+  "fusion_get_preferences",+  "fusion_import_electronics",+  "fusion_import_step",+  "fusion_inspect_bodies",+  "fusion_install_fusion",+  "fusion_install_library_bundle",+  "fusion_interactive",+  "fusion_kill",+  "fusion_library_manager",+  "fusion_library_parts",+  "fusion_list_cloud_files",+  "fusion_list_cloud_projects",+  "fusion_list_text_commands",+  "fusion_live_feed",+  "fusion_make_3d_package",+  "fusion_make_showcase_project",+  "fusion_make_test_project",+  "fusion_managed_library_create",+  "fusion_managed_library_link",+  "fusion_managed_library_pull",+  "fusion_managed_library_push",+  "fusion_managed_library_unlink",+  "fusion_mcp_call",+  "fusion_mcp_enable",+  "fusion_mcp_resources",+  "fusion_mcp_status",+  "fusion_mcp_tools",+  "fusion_move_parts",+  "fusion_narrate",+  "fusion_new_electronics_from_eagle",+  "fusion_notify_owner",+  "fusion_open_board",+  "fusion_open_by_urn",+  "fusion_open_by_urn_note",+  "fusion_open_cloud_file",+  "fusion_open_design",+  "fusion_open_electronics",+  "fusion_open_lbr",+  "fusion_open_schematic",+  "fusion_physical_properties",+  "fusion_place_vias",+  "fusion_plan_library_import",+  "fusion_plan_route",+  "fusion_pour_state",+  "fusion_preflight_lbr",+  "fusion_prefs_close",+  "fusion_prefs_open",+  "fusion_prewarm",+  "fusion_prove_result",+  "fusion_readiness",+  "fusion_release_seat",+  "fusion_remove_pours",+  "fusion_reset_workspace",+  "fusion_resolve_seat_dialog",+  "fusion_restart_for_addin",+  "fusion_ripup_nets",+  "fusion_route_net",+  "fusion_routing_drc",+  "fusion_routing_state",+  "fusion_run_modeling_script",+  "fusion_save_to_cloud",+  "fusion_schematic",+  "fusion_schematic_plan",+  "fusion_schematic_run",+  "fusion_screenshot_fusion",+  "fusion_search_cloud_files",+  "fusion_set_auto_update",+  "fusion_set_design_rules",+  "fusion_set_driving_badge",+  "fusion_set_parameter",+  "fusion_set_preference",+  "fusion_set_seat_takeover",+  "fusion_set_window_sizing",+  "fusion_show_2d_board",+  "fusion_show_3d_board",+  "fusion_show_3d_package",+  "fusion_show_attributes",+  "fusion_show_footprint",+  "fusion_show_library",+  "fusion_show_project",+  "fusion_show_schematic",+  "fusion_show_symbol",+  "fusion_signin",+  "fusion_signin_2fa",+  "fusion_start",+  "fusion_state",+  "fusion_stop",+  "fusion_take_screenshot",+  "fusion_uninstall",+  "fusion_verb_times",+  "fusion_view",+  "fusion_walk_cloud_tree",+  "fusion_web_signin"+ ]+}\ No newline at end of file
reference/kicad-1.0.58.jsonadded+127
@@ -0,0 +1,127 @@+{+ "bridge": "kicad",+ "version": "1.0.58",+ "verbs": [+  "kicad_3d_view",+  "kicad_3d_viewer_settings",+  "kicad_add_track",+  "kicad_add_via",+  "kicad_add_zone",+  "kicad_adom_library_heal",+  "kicad_adom_library_status",+  "kicad_analyze_connections",+  "kicad_apply_zone_plan",+  "kicad_autoroute",+  "kicad_board_pads",+  "kicad_bridge_call",+  "kicad_bridge_status",+  "kicad_check_for_updates",+  "kicad_click",+  "kicad_close",+  "kicad_close_3d_viewer",+  "kicad_close_footprint_editor",+  "kicad_close_symbol_editor",+  "kicad_demo",+  "kicad_describe",+  "kicad_diagnostics",+  "kicad_dismiss_dialogs",+  "kicad_enable_software_opengl",+  "kicad_errors",+  "kicad_export_bom_csv",+  "kicad_export_gerber",+  "kicad_export_molecule",+  "kicad_export_part",+  "kicad_export_pdf",+  "kicad_export_pos",+  "kicad_export_step",+  "kicad_export_svg",+  "kicad_extract_netlist",+  "kicad_find_connections",+  "kicad_fix_keyboard",+  "kicad_format_upgrade",+  "kicad_freerouting",+  "kicad_get_settings",+  "kicad_install_footprint",+  "kicad_install_library",+  "kicad_install_library_bundle",+  "kicad_install_plugin",+  "kicad_install_symbol",+  "kicad_ipc_api",+  "kicad_launch",+  "kicad_lint_board",+  "kicad_lint_library",+  "kicad_lint_schematic",+  "kicad_list_design_rules",+  "kicad_list_footprints",+  "kicad_list_symbols",+  "kicad_list_versions",+  "kicad_log_tail",+  "kicad_make_part_project",+  "kicad_menu_items",+  "kicad_model_check",+  "kicad_move_footprint",+  "kicad_open_3d_viewer",+  "kicad_open_board",+  "kicad_open_editors",+  "kicad_open_footprint_editor",+  "kicad_open_schematic",+  "kicad_open_symbol_editor",+  "kicad_pcm_add_repository",+  "kicad_pcm_install",+  "kicad_pcm_list",+  "kicad_pcm_remove_repository",+  "kicad_pcm_uninstall",+  "kicad_place_footprint",+  "kicad_placement_state",+  "kicad_placement_validate",+  "kicad_plugin_diagnose",+  "kicad_prewarm",+  "kicad_progress",+  "kicad_readiness",+  "kicad_refresh_board_views",+  "kicad_remove_route",+  "kicad_remove_zone",+  "kicad_render_board",+  "kicad_rescan_libraries",+  "kicad_route",+  "kicad_route_net",+  "kicad_routing_state",+  "kicad_routing_validate",+  "kicad_run_drc",+  "kicad_run_erc",+  "kicad_sch_delete",+  "kicad_sch_label",+  "kicad_sch_move",+  "kicad_sch_place_symbol",+  "kicad_sch_set_property",+  "kicad_sch_wire",+  "kicad_screenshot_all",+  "kicad_select_clear",+  "kicad_select_net",+  "kicad_send_key",+  "kicad_set_design_rules",+  "kicad_set_settings",+  "kicad_show_2d_board",+  "kicad_show_3d_board",+  "kicad_show_3d_chip",+  "kicad_show_footprint",+  "kicad_show_library",+  "kicad_show_project",+  "kicad_show_schematic",+  "kicad_show_symbol",+  "kicad_silk_fields_batch",+  "kicad_silk_graphics_batch",+  "kicad_silk_text_batch",+  "kicad_state",+  "kicad_status",+  "kicad_taskbar_repair",+  "kicad_text_bounds",+  "kicad_trace_net",+  "kicad_uninstall",+  "kicad_uninstall_library",+  "kicad_upgrade",+  "kicad_verb_times",+  "kicad_window_info",+  "kicad_zone_state"+ ]+}\ No newline at end of file
schema/board.schema.jsonadded+42
@@ -0,0 +1,42 @@+{+  "$schema": "https://json-schema.org/draft/2020-12/schema",+  "$id": "https://wiki.adom.inc/adom/eda-bridge-standard/schema/board.schema.json",+  "title": "Board model (routing_state)",+  "description": "One PCB as every bridge's <prefix>_routing_state returns it. Millimetres, the tool's own frame (y up), absolute board coordinates. Copper layers carry KiCad's names whatever the tool: F.Cu, In1.Cu..In30.Cu, B.Cu, and *.Cu for a pad on every copper layer. Reference: altium-bridge 0.2.75.",+  "type": "object",+  "required": ["units", "revision", "nets", "tracks", "arcs", "vias", "pads", "components", "polygons", "outline", "outlineEdges", "unconnected"],+  "$defs": {+    "xy": {"type": "object", "required": ["x", "y"], "properties": {"x": {"type": "number"}, "y": {"type": "number"}}},+    "pt": {"type": "array", "items": {"type": "number"}, "minItems": 2, "maxItems": 2, "description": "[x, y] mm"},+    "layer": {"type": "string", "description": "F.Cu | In<n>.Cu | B.Cu | *.Cu for copper; a non-copper layer is the tool's own name or id (L57 on Altium is Mechanical 1)"},+    "net": {"type": "string", "description": "the net's name; empty for no net (a hatched pour's pieces, free copper)"}+  },+  "properties": {+    "units": {"const": "mm"},+    "file": {"type": ["string", "null"], "description": "the board file's path"},+    "boardId": {"type": ["string", "null"], "description": "identifies the board; the file path where there is one"},+    "revision": {"type": "string", "description": "a hash of copper, parts and outline: stable for an unchanged board. Edit verbs take it back as expectedRevision and refuse with stale_board when the board moved on"},+    "counts": {"type": "object", "description": "tracks, arcs, vias, pads, components, polygons, nets"},+    "unconnected": {"type": "integer", "description": "connections still to make, computed from the copper itself (pads, tracks, vias and pours joined where they touch on a shared layer)"},+    "splitNets": {"type": "array", "description": "nets whose copper is in more than one piece"},+    "nets": {"type": "array", "items": {"type": "string"}},+    "tracks": {"type": "array", "items": {"type": "object", "required": ["start", "end", "width", "layer", "net"],+      "properties": {"start": {"$ref": "#/$defs/xy"}, "end": {"$ref": "#/$defs/xy"}, "width": {"type": "number"}, "layer": {"$ref": "#/$defs/layer"}, "layerId": {"description": "the tool's own layer id (optional)"}, "net": {"$ref": "#/$defs/net"}}}},+    "arcs": {"type": "array", "items": {"type": "object", "required": ["center", "radius", "startAngle", "endAngle", "width", "layer", "net"],+      "properties": {"center": {"$ref": "#/$defs/xy"}, "radius": {"type": "number"}, "startAngle": {"type": "number", "description": "degrees, counterclockwise from +x"}, "endAngle": {"type": "number"}, "width": {"type": "number"}, "layer": {"$ref": "#/$defs/layer"}, "net": {"$ref": "#/$defs/net"}}}},+    "vias": {"type": "array", "items": {"type": "object", "required": ["position", "size", "drill", "layers", "net"],+      "properties": {"position": {"$ref": "#/$defs/xy"}, "size": {"type": "number", "description": "diameter mm"}, "drill": {"type": "number"}, "layers": {"type": "array", "items": {"$ref": "#/$defs/layer"}, "description": "[from, to]"}, "net": {"$ref": "#/$defs/net"}}}},+    "pads": {"type": "array", "items": {"type": "object", "required": ["ref", "name", "x", "y", "sizeX", "sizeY", "rotation", "layer", "drill", "net"],+      "properties": {"ref": {"type": "string", "description": "the part's designator; empty for a free pad (a mounting hole, a fiducial)"}, "name": {"type": "string", "description": "the pad's number/name"},+        "x": {"type": "number"}, "y": {"type": "number"}, "sizeX": {"type": "number"}, "sizeY": {"type": "number"}, "rotation": {"type": "number", "description": "degrees"},+        "layer": {"$ref": "#/$defs/layer"}, "drill": {"type": "number", "description": "0 for SMD"}, "net": {"$ref": "#/$defs/net"},+        "shape": {"type": "string", "description": "round | rect | roundrect | octagonal | other. A custom-shape pad (copper drawn as regions) is reported as its bounding rectangle: rect, rotation 0"}}}},+    "components": {"type": "array", "items": {"type": "object", "required": ["ref", "x", "y", "rotation", "side"],+      "properties": {"ref": {"type": "string"}, "x": {"type": "number"}, "y": {"type": "number"}, "rotation": {"type": "number"}, "side": {"enum": ["top", "bottom"]}, "pattern": {"type": "string", "description": "the footprint's name"}, "has3d": {"type": ["boolean", "null"]}}}},+    "polygons": {"type": "array", "description": "pours as drawn (their boundary). What was actually poured is the pours model (pour_state)", "items": {"type": "object", "required": ["net", "layer", "points"],+      "properties": {"net": {"$ref": "#/$defs/net"}, "layer": {"$ref": "#/$defs/layer"}, "points": {"type": "array", "items": {"$ref": "#/$defs/pt"}}}}},+    "outline": {"type": "array", "items": {"$ref": "#/$defs/pt"}, "description": "the board outline's vertices in order"},+    "outlineEdges": {"type": "array", "description": "the outline as edges: line, or arc (center, radius, angle1, angle2 in degrees). Sample arcs finely (every 2 degrees) before measuring against them", "items": {"type": "object", "required": ["kind", "start"],+      "properties": {"kind": {"enum": ["line", "arc"]}, "start": {"$ref": "#/$defs/pt"}, "end": {"$ref": "#/$defs/pt"}, "center": {"$ref": "#/$defs/pt"}, "radius": {"type": "number"}, "angle1": {"type": "number"}, "angle2": {"type": "number"}}}}+  }+}
schema/companions.schema.jsonadded+51
@@ -0,0 +1,51 @@+{+  "$schema": "https://json-schema.org/draft/2020-12/schema",+  "$id": "https://wiki.adom.inc/adom/eda-bridge-standard/schema/companions.schema.json",+  "title": "Companion models: stack-up, pours, 3D bodies, sheets, netlist",+  "description": "What the checks need beside the board model. Each is one verb's reply (or its data). Millimetres unless a field's name says otherwise. Reference: altium-bridge 0.2.75.",+  "$defs": {+    "stack": {+      "title": "Stack-up (layer_stack)",+      "type": "object",+      "required": ["copperLayers", "layers"],+      "properties": {+        "copperLayers": {"type": "integer"},+        "totalUm": {"type": "number", "description": "board thickness, micrometres"},+        "layers": {"type": "array", "description": "the copper layers top to bottom, each with the dielectric BELOW it", "items": {"type": "object", "required": ["name", "kind", "copperUm"],+          "properties": {"name": {"type": "string"}, "kind": {"enum": ["signal", "plane"]}, "copperUm": {"type": "number"},+            "dielectricUm": {"type": "number", "description": "to the next copper layer; absent on the last"}, "dk1000": {"type": "number", "description": "the dielectric constant x 1000"}, "material": {"type": "string"},+            "mid": {"type": "integer", "description": "a signal layer's inner index (In<mid>.Cu)"}, "plane": {"type": "integer", "description": "an internal plane's index"}, "net": {"type": "string", "description": "a plane's net"}}}}+      }+    },+    "pours": {+      "title": "Pours as poured (pour_state)",+      "type": "object",+      "properties": {"zones": {"type": "array", "items": {"type": "object", "required": ["net", "layer", "fills"],+        "properties": {"net": {"type": "string"}, "name": {"type": "string"}, "layer": {"type": "string"}, "outline": {"type": "array"}, "filledMm2": {"type": "number"}, "filled": {"type": "boolean"},+          "fills": {"type": "array", "description": "the copper actually poured: each region's outline and its holes", "items": {"type": "object", "properties": {"outline": {"type": "array"}, "holes": {"type": "array"}}}}}}}}+    },+    "bodies": {+      "title": "3D bodies",+      "type": "array",+      "description": "Each part's 3D bodies as boxes: footprint rectangle and height range above the surface it sits on (a part with several bodies has several). Altium's agent sends rows [part, layer, overall, standoff, l, b, r, t] in internal units; the standard form is this, in mm.",+      "items": {"type": "object", "required": ["ref", "side", "x0", "y0", "x1", "y1", "z0", "z1"],+        "properties": {"ref": {"type": "string"}, "side": {"enum": ["top", "bottom"]}, "x0": {"type": "number"}, "y0": {"type": "number"}, "x1": {"type": "number"}, "y1": {"type": "number"},+          "z0": {"type": "number", "description": "the body's bottom above the surface (standoff)"}, "z1": {"type": "number", "description": "its top"}}}+    },+    "sheetParts": {+      "title": "Placed parts of a schematic sheet (read_sheet_file components)",+      "type": "array",+      "items": {"type": "object", "required": ["designator", "pins"],+        "properties": {"designator": {"type": "string"}, "libReference": {"type": "string"}, "comment": {"type": "string"}, "uniqueId": {"type": "string"},+          "pins": {"type": "array", "items": {"type": "object", "required": ["number", "name", "electrical"],+            "properties": {"number": {"type": "string"}, "name": {"type": "string"},+              "electrical": {"description": "Altium's ELECTRICAL code (0 input, 1 io, 2 output, 3 open_collector, 4 passive, 5 hiz, 6 open_emitter, 7 power) or the name", "type": ["integer", "string"]}}}}}}+    },+    "netlist": {+      "title": "The compiled netlist (netlist nets)",+      "type": "object",+      "description": "{net: [\"U1.8\", \"C3.1\", ...]}: every net with its pins as Designator.PinNumber, across every sheet, the hierarchy resolved",+      "additionalProperties": {"type": "array", "items": {"type": "string"}}+    }+  }+}
tools/equivalents.jsonadded+162
@@ -0,0 +1,162 @@+{+ "fusion360": {+  "apply_pours": [+   "change_pours",+   "partial"+  ],+  "board_summary": [+   "board_info",+   "partial"+  ],+  "build_schematic": [+   "schematic_plan + schematic_run",+   "partial"+  ],+  "close": [+   "stop",+   "exact"+  ],+  "close_document": [+   "close_document",+   "exact"+  ],+  "dismiss_dialogs": [+   "dismiss_blocking_dialogs",+   "exact"+  ],+  "export_bom": [+   "export_bom / board_bom",+   "exact"+  ],+  "export_gerber": [+   "export_gerbers",+   "exact"+  ],+  "export_manufacturing": [+   "export_gerbers + export_cpl + export_bom",+   "partial"+  ],+  "install": [+   "install_fusion",+   "exact"+  ],+  "launch": [+   "start",+   "exact"+  ],+  "layer_stack": [+   "board_stackup",+   "exact"+  ],+  "read_board": [+   "board_info / get_design_info",+   "partial"+  ],+  "read_schematic": [+   "schematic",+   "partial"+  ]+ },+ "kicad": {+  "apply_pours": [+   "add_zone / apply_zone_plan",+   "exact"+  ],+  "close": [+   "close",+   "exact"+  ],+  "cross_check": [+   "analyze_connections",+   "partial"+  ],+  "export_bom": [+   "export_bom_csv",+   "partial"+  ],+  "export_manufacturing": [+   "export_gerber + export_pos",+   "partial"+  ],+  "export_pdf": [+   "export_pdf",+   "exact"+  ],+  "list_library": [+   "list_footprints / list_symbols",+   "partial"+  ],+  "move_parts": [+   "move_footprint",+   "exact"+  ],+  "net_connections": [+   "trace_net / find_connections",+   "partial"+  ],+  "netlist": [+   "extract_netlist",+   "exact"+  ],+  "pcb_report": [+   "errors",+   "partial"+  ],+  "place_on_pcb": [+   "place_footprint",+   "exact"+  ],+  "place_vias": [+   "add_via",+   "exact"+  ],+  "placement_check": [+   "placement_validate",+   "partial"+  ],+  "pour_state": [+   "zone_state",+   "exact"+  ],+  "read_rules": [+   "list_design_rules",+   "exact"+  ],+  "remove_items": [+   "remove_route",+   "partial"+  ],+  "remove_pours": [+   "remove_zone",+   "exact"+  ],+  "review_board": [+   "lint_board",+   "partial"+  ],+  "route_net": [+   "route_net / add_track",+   "exact"+  ],+  "routing_drc": [+   "routing_validate",+   "partial"+  ],+  "sch_connect": [+   "sch_wire / sch_label",+   "partial"+  ],+  "sch_edit": [+   "sch_set_property / sch_move / sch_delete",+   "partial"+  ],+  "sch_place_part": [+   "sch_place_symbol",+   "exact"+  ],+  "schematic_review": [+   "lint_schematic",+   "partial"+  ]+ }+}\ No newline at end of file
tools/generate.pyadded+128
@@ -0,0 +1,128 @@+#!/usr/bin/env python3+"""Generate the standard's verb files from the reference bridge's own catalog.++The reference is the Altium bridge (the most complete): reference/altium-<version>.json is its `altium_describe`+reply. Every verb gets a tier (tools/tiers.json); KiCad and Fusion coverage comes from their verb lists+(reference/<bridge>-<version>.json) and the hand-made map of verbs that mean the same under another name+(tools/equivalents.json).++Writes verbs/standard.json, verbs/coverage.json, docs/coverage.md and the coverage block in README.md.+Run it after refreshing any reference file:  python3 tools/generate.py+"""+import glob+import json+import os+import re++ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))+TIERS = {+    "core": "Read and edit the design. Every bridge provides these: they are how an AI sees and changes a board.",+    "native": "Wraps the EDA tool's own feature (its DRC, ERC, Gerbers, pours, Update PCB). Every tool has one; the bridge exposes it under this name.",+    "checks": "Engineering computed from the board model, not by the tool: one shared engine, so every bridge that serves the model gets them all.",+    "app": "Driving the application itself (launch, licence, dialogs, windows, views). Each bridge has its own; the names are shared where the job is.",+    "platform": "Specific to one tool's platform (Altium 365, OutJobs, integrated libraries). Not part of the standard; listed so nothing is lost.",+}+ORDER = ["core", "native", "checks", "app", "platform"]+++def load(name):+    with open(os.path.join(ROOT, name)) as f:+        return json.load(f)+++def latest(bridge):+    files = sorted(glob.glob(os.path.join(ROOT, "reference", f"{bridge}-*.json")))+    return load(os.path.relpath(files[-1], ROOT))+++def strip(name, prefix):+    return name[len(prefix):] if name.startswith(prefix) else name+++def main():+    ref = latest("altium")+    tiers = load("tools/tiers.json")+    eq = load("tools/equivalents.json")+    others = {b: latest(b) for b in ("kicad", "fusion360")}+    prefixes = {"kicad": "kicad_", "fusion360": "fusion_"}+    have = {b: {strip(v, prefixes[b]) for v in o["verbs"]} for b, o in others.items()}++    standard = []+    for v in sorted(ref["verbs"], key=lambda v: (ORDER.index(tiers[strip(v["name"], "altium_")]), v["name"])):+        name = strip(v["name"], "altium_")+        standard.append({+            "name": name, "tier": tiers[name], "summary": v.get("summary", ""),+            "input": v.get("input", {}), "output": v.get("output", {}), "hint": v.get("hint", ""),+            "pitfalls": v.get("pitfalls", []), "related": [strip(r, "altium_") for r in v.get("related", [])],+            "example": v.get("example", {}), "longRunning": v.get("longRunning", False), "timeoutSeconds": v.get("timeoutSeconds"),+        })++    coverage = []+    for s in standard:+        row = {"name": s["name"], "tier": s["tier"], "altium": "altium_" + s["name"]}+        for b in others:+            if s["name"] in have[b]:+                row[b] = {"verb": prefixes[b] + s["name"], "match": "exact"}+            elif s["name"] in eq.get(b, {}):+                verb, match = eq[b][s["name"]]+                row[b] = {"verb": " / ".join(prefixes[b] + p.strip() if not p.strip().startswith(prefixes[b]) else p.strip() for p in re.split(r"\s*[/+]\s*", verb)), "match": match}+            else:+                row[b] = None+        coverage.append(row)++    meta = {"reference": {"bridge": "altium", "version": ref["version"]},+            "compared": {b: o["version"] for b, o in others.items()}, "tiers": TIERS}+    os.makedirs(os.path.join(ROOT, "verbs"), exist_ok=True)+    with open(os.path.join(ROOT, "verbs", "standard.json"), "w") as f:+        json.dump({**meta, "verbs": standard}, f, indent=1)+        f.write("\n")+    with open(os.path.join(ROOT, "verbs", "coverage.json"), "w") as f:+        json.dump({**meta, "verbs": coverage}, f, indent=1)+        f.write("\n")++    # counts per tier and bridge+    def count(b, tier, matches=("exact", "partial")):+        return sum(1 for r in coverage if r["tier"] == tier and r.get(b) and r[b]["match"] in matches)+    lines = ["| Tier | Verbs | Altium | KiCad | Fusion 360 |", "|---|---|---|---|---|"]+    tot = {"altium": 0, "kicad": 0, "fusion360": 0, "all": 0}+    for t in ORDER:+        n = sum(1 for r in coverage if r["tier"] == t)+        k, fu = count("kicad", t), count("fusion360", t)+        lines.append(f"| **{t}** | {n} | {n} | {k} | {fu} |")+        if t != "platform":+            tot["all"] += n; tot["altium"] += n; tot["kicad"] += k; tot["fusion360"] += fu+    lines.append(f"| standard (all but platform) | {tot['all']} | {tot['altium']} | {tot['kicad']} | {tot['fusion360']} |")+    summary = "\n".join(lines)++    # docs/coverage.md: the full table+    md = [f"# Coverage: every standard verb on each bridge",+          "",+          f"Reference: Altium bridge {ref['version']}. Compared: KiCad bridge {others['kicad']['version']}, Fusion 360 bridge {others['fusion360']['version']}.",+          "`exact` is the same job under the same or an equivalent name; `partial` does part of it or does it differently; blank is missing.",+          "Generated by `tools/generate.py` from `verbs/coverage.json`; do not edit by hand.", "", summary, ""]+    for t in ORDER:+        md += [f"## {t}", "", TIERS[t], "", "| Verb | What it does | KiCad | Fusion 360 |", "|---|---|---|---|"]+        for r, s in zip(coverage, standard):+            if r["tier"] != t:+                continue+            cell = lambda c: "" if not c else (f"`{c['verb']}`" + (" (partial)" if c["match"] == "partial" else ""))+            summ = s["summary"].replace("|", "/").replace("\n", " ")+            if len(summ) > 160:+                summ = summ[:157].rsplit(" ", 1)[0] + "..."+            md.append(f"| `{r['name']}` | {summ} | {cell(r.get('kicad'))} | {cell(r.get('fusion360'))} |")+        md.append("")+    with open(os.path.join(ROOT, "docs", "coverage.md"), "w") as f:+        f.write("\n".join(md))++    # README coverage block+    readme = os.path.join(ROOT, "README.md")+    if os.path.exists(readme):+        text = open(readme).read()+        block = f"<!-- coverage:start (tools/generate.py writes this) -->\n{summary}\n<!-- coverage:end -->"+        text = re.sub(r"<!-- coverage:start.*?<!-- coverage:end -->", lambda _: block, text, flags=re.S)+        open(readme, "w").write(text)+    print(f"standard: {len(standard)} verbs ({tot['all']} in the standard); kicad covers {tot['kicad']}, fusion {tot['fusion360']}")+++if __name__ == "__main__":+    main()
tools/tiers.jsonadded+203
@@ -0,0 +1,203 @@+{+ "a365_configure": "platform",+ "a365_files_upload": "platform",+ "a365_library_overview": "platform",+ "a365_library_schema": "platform",+ "a365_logout": "platform",+ "a365_oauth_begin": "platform",+ "a365_oauth_cancel": "platform",+ "a365_oauth_status": "platform",+ "a365_query": "platform",+ "a365_refresh": "platform",+ "a365_status": "platform",+ "activate_document": "app",+ "add_body": "core",+ "add_models": "core",+ "add_rule": "native",+ "add_symbol_art": "core",+ "agent_recover": "app",+ "agent_start": "app",+ "agent_status": "app",+ "agent_stop": "app",+ "animate_view": "app",+ "annotate": "native",+ "apply_pours": "native",+ "apply_silkscreen": "core",+ "assembly_check": "checks",+ "auto_classes": "checks",+ "autoroute": "native",+ "board_features": "checks",+ "board_outline": "core",+ "board_report": "checks",+ "board_shape": "core",+ "board_summary": "core",+ "bom": "checks",+ "build_board": "checks",+ "build_footprint": "checks",+ "build_schematic": "checks",+ "build_symbol_modes": "native",+ "check_dialogs": "app",+ "check_orientation": "checks",+ "cleanup_libs": "native",+ "clone_symbol": "core",+ "close": "app",+ "close_document": "app",+ "compile": "native",+ "compile_intlib": "platform",+ "component_classes": "native",+ "component_data": "core",+ "component_pins": "core",+ "connect_workspace": "app",+ "copper_balance": "checks",+ "copper_layers": "core",+ "copy_footprint": "core",+ "create_footprint": "core",+ "create_symbol": "core",+ "cross_check": "checks",+ "current_capacity": "checks",+ "demo": "app",+ "describe": "app",+ "diagnostics": "app",+ "dialog_log": "app",+ "diff_pairs": "native",+ "dismiss_dialogs": "app",+ "doc_params": "core",+ "ensure_licensed": "app",+ "eval": "app",+ "export_bom": "native",+ "export_gerber": "native",+ "export_manufacturing": "native",+ "export_pdf": "native",+ "export_step": "native",+ "extension_ping": "app",+ "extract_intlib": "platform",+ "fab_check": "checks",+ "find_space": "checks",+ "find_symbol": "core",+ "footprint_from_land_pattern": "checks",+ "footprint_primitives": "core",+ "foreground": "app",+ "height_check": "checks",+ "impedance": "checks",+ "import_kicad": "platform",+ "import_part": "checks",+ "install": "app",+ "install_extension": "app",+ "install_library": "native",+ "install_library_bundle": "native",+ "install_receipt": "app",+ "instances": "app",+ "jlcpcb_order": "checks",+ "launch": "app",+ "layer_stack": "core",+ "layers": "core",+ "layers_in_use": "core",+ "length_tune": "checks",+ "license": "app",+ "license_status": "app",+ "list_commands": "app",+ "list_designators": "core",+ "list_installed_libraries": "native",+ "list_library": "core",+ "list_nets": "core",+ "move_parts": "core",+ "net_classes": "native",+ "net_connections": "core",+ "net_lengths": "checks",+ "netlist": "native",+ "normalize_bodies": "core",+ "open_document": "app",+ "open_project": "app",+ "outjob_info": "platform",+ "panel": "app",+ "panelize": "native",+ "part_parameters": "core",+ "pcb_report": "native",+ "pin_pad_check": "checks",+ "place_on_pcb": "core",+ "place_on_sch": "core",+ "place_vias": "core",+ "placement_check": "checks",+ "plate_vias": "core",+ "pour_state": "native",+ "progress": "app",+ "read_board": "core",+ "read_rules": "core",+ "read_schematic": "core",+ "read_sheet_file": "checks",+ "read_symbol": "core",+ "readiness": "app",+ "remove_footprint": "core",+ "remove_items": "core",+ "remove_pours": "native",+ "remove_symbol": "core",+ "rename_footprint": "core",+ "rename_symbol": "core",+ "replicate_layout": "native",+ "repour": "native",+ "return_path": "checks",+ "review_board": "checks",+ "ripup_nets": "core",+ "route_net": "core",+ "route_pair": "checks",+ "routing_drc": "native",+ "routing_state": "core",+ "rules_by_kind": "core",+ "run_command": "app",+ "run_drc": "native",+ "run_erc": "native",+ "run_outjob": "platform",+ "run_script": "app",+ "save": "app",+ "sch_connect": "core",+ "sch_edit": "core",+ "sch_place_part": "core",+ "sch_sheet_symbol": "core",+ "sch_swap_part": "core",+ "sch_zoom": "app",+ "schematic_data": "core",+ "schematic_review": "checks",+ "select_net": "app",+ "selection": "app",+ "selftest_script_error": "app",+ "set_design_rules": "native",+ "set_layer_visibility": "app",+ "set_models": "core",+ "set_outline": "core",+ "set_params": "core",+ "set_track_width": "core",+ "set_visibility": "core",+ "setup_trial": "app",+ "sheet_params": "core",+ "show_2d_board": "app",+ "show_3d_board": "app",+ "show_3d_chip": "app",+ "show_footprint": "app",+ "show_library": "app",+ "show_project": "app",+ "show_schematic": "app",+ "show_symbol": "app",+ "signin": "app",+ "signin_status": "app",+ "silk_designators": "checks",+ "silk_state": "core",+ "silkscreen_prep": "native",+ "state": "app",+ "status": "app",+ "swap_footprint": "core",+ "symbol_mode": "native",+ "symbol_prims": "core",+ "teardrops": "native",+ "test_board": "checks",+ "thermal_vias": "checks",+ "uninstall": "app",+ "uninstall_library": "native",+ "update_pcb": "native",+ "variants": "platform",+ "via_fence": "checks",+ "window_info": "app",+ "workspace_place": "platform",+ "workspace_search": "platform",+ "xcopy_footprint": "core",+ "zoom_to": "app"+}\ No newline at end of file
verbs/coverage.jsonadded+1708
@@ -0,0 +1,1708 @@+{+ "reference": {+  "bridge": "altium",+  "version": "0.2.75"+ },+ "compared": {+  "kicad": "1.0.58",+  "fusion360": "1.11.44"+ },+ "tiers": {+  "core": "Read and edit the design. Every bridge provides these: they are how an AI sees and changes a board.",+  "native": "Wraps the EDA tool's own feature (its DRC, ERC, Gerbers, pours, Update PCB). Every tool has one; the bridge exposes it under this name.",+  "checks": "Engineering computed from the board model, not by the tool: one shared engine, so every bridge that serves the model gets them all.",+  "app": "Driving the application itself (launch, licence, dialogs, windows, views). Each bridge has its own; the names are shared where the job is.",+  "platform": "Specific to one tool's platform (Altium 365, OutJobs, integrated libraries). Not part of the standard; listed so nothing is lost."+ },+ "verbs": [+  {+   "name": "add_body",+   "tier": "core",+   "altium": "altium_add_body",+   "kicad": null,+   "fusion360": null+  },+  {+   "name": "add_models",+   "tier": "core",+   "altium": "altium_add_models",+   "kicad": null,+   "fusion360": null+  },+  {+   "name": "add_symbol_art",+   "tier": "core",+   "altium": "altium_add_symbol_art",+   "kicad": null,+   "fusion360": null+  },+  {+   "name": "apply_silkscreen",+   "tier": "core",+   "altium": "altium_apply_silkscreen",+   "kicad": null,+   "fusion360": null+  },+  {+   "name": "board_outline",+   "tier": "core",+   "altium": "altium_board_outline",+   "kicad": null,+   "fusion360": null+  },+  {+   "name": "board_shape",+   "tier": "core",+   "altium": "altium_board_shape",+   "kicad": null,+   "fusion360": null+  },+  {+   "name": "board_summary",+   "tier": "core",+   "altium": "altium_board_summary",+   "kicad": null,+   "fusion360": {+    "verb": "fusion_board_info",+    "match": "partial"+   }+  },+  {+   "name": "clone_symbol",+   "tier": "core",+   "altium": "altium_clone_symbol",+   "kicad": null,+   "fusion360": null+  },+  {+   "name": "component_data",+   "tier": "core",+   "altium": "altium_component_data",+   "kicad": null,+   "fusion360": null+  },+  {+   "name": "component_pins",+   "tier": "core",+   "altium": "altium_component_pins",+   "kicad": null,+   "fusion360": null+  },+  {+   "name": "copper_layers",+   "tier": "core",+   "altium": "altium_copper_layers",+   "kicad": null,+   "fusion360": null+  },+  {+   "name": "copy_footprint",+   "tier": "core",+   "altium": "altium_copy_footprint",+   "kicad": null,+   "fusion360": null+  },+  {+   "name": "create_footprint",+   "tier": "core",+   "altium": "altium_create_footprint",+   "kicad": null,+   "fusion360": null+  },+  {+   "name": "create_symbol",+   "tier": "core",+   "altium": "altium_create_symbol",+   "kicad": null,+   "fusion360": null+  },+  {+   "name": "doc_params",+   "tier": "core",+   "altium": "altium_doc_params",+   "kicad": null,+   "fusion360": null+  },+  {+   "name": "find_symbol",+   "tier": "core",+   "altium": "altium_find_symbol",+   "kicad": null,+   "fusion360": null+  },+  {+   "name": "footprint_primitives",+   "tier": "core",+   "altium": "altium_footprint_primitives",+   "kicad": null,+   "fusion360": null+  },+  {+   "name": "layer_stack",+   "tier": "core",+   "altium": "altium_layer_stack",+   "kicad": null,+   "fusion360": {+    "verb": "fusion_board_stackup",+    "match": "exact"+   }+  },+  {+   "name": "layers",+   "tier": "core",+   "altium": "altium_layers",+   "kicad": null,+   "fusion360": null+  },+  {+   "name": "layers_in_use",+   "tier": "core",+   "altium": "altium_layers_in_use",+   "kicad": null,+   "fusion360": null+  },+  {+   "name": "list_designators",+   "tier": "core",+   "altium": "altium_list_designators",+   "kicad": null,+   "fusion360": null+  },+  {+   "name": "list_library",+   "tier": "core",+   "altium": "altium_list_library",+   "kicad": {+    "verb": "kicad_list_footprints / kicad_list_symbols",+    "match": "partial"+   },+   "fusion360": null+  },+  {+   "name": "list_nets",+   "tier": "core",+   "altium": "altium_list_nets",+   "kicad": null,+   "fusion360": null+  },+  {+   "name": "move_parts",+   "tier": "core",+   "altium": "altium_move_parts",+   "kicad": {+    "verb": "kicad_move_footprint",+    "match": "exact"+   },+   "fusion360": {+    "verb": "fusion_move_parts",+    "match": "exact"+   }+  },+  {+   "name": "net_connections",+   "tier": "core",+   "altium": "altium_net_connections",+   "kicad": {+    "verb": "kicad_trace_net / kicad_find_connections",+    "match": "partial"+   },+   "fusion360": null+  },+  {+   "name": "normalize_bodies",+   "tier": "core",+   "altium": "altium_normalize_bodies",+   "kicad": null,+   "fusion360": null+  },+  {+   "name": "part_parameters",+   "tier": "core",+   "altium": "altium_part_parameters",+   "kicad": null,+   "fusion360": null+  },+  {+   "name": "place_on_pcb",+   "tier": "core",+   "altium": "altium_place_on_pcb",+   "kicad": {+    "verb": "kicad_place_footprint",+    "match": "exact"+   },+   "fusion360": null+  },+  {+   "name": "place_on_sch",+   "tier": "core",+   "altium": "altium_place_on_sch",+   "kicad": null,+   "fusion360": null+  },+  {+   "name": "place_vias",+   "tier": "core",+   "altium": "altium_place_vias",+   "kicad": {+    "verb": "kicad_add_via",+    "match": "exact"+   },+   "fusion360": {+    "verb": "fusion_place_vias",+    "match": "exact"+   }+  },+  {+   "name": "plate_vias",+   "tier": "core",+   "altium": "altium_plate_vias",+   "kicad": null,+   "fusion360": null+  },+  {+   "name": "read_board",+   "tier": "core",+   "altium": "altium_read_board",+   "kicad": null,+   "fusion360": {+    "verb": "fusion_board_info / fusion_get_design_info",+    "match": "partial"+   }+  },+  {+   "name": "read_rules",+   "tier": "core",+   "altium": "altium_read_rules",+   "kicad": {+    "verb": "kicad_list_design_rules",+    "match": "exact"+   },+   "fusion360": null+  },+  {+   "name": "read_schematic",+   "tier": "core",+   "altium": "altium_read_schematic",+   "kicad": null,+   "fusion360": {+    "verb": "fusion_schematic",+    "match": "partial"+   }+  },+  {+   "name": "read_symbol",+   "tier": "core",+   "altium": "altium_read_symbol",+   "kicad": null,+   "fusion360": null+  },+  {+   "name": "remove_footprint",+   "tier": "core",+   "altium": "altium_remove_footprint",+   "kicad": null,+   "fusion360": null+  },+  {+   "name": "remove_items",+   "tier": "core",+   "altium": "altium_remove_items",+   "kicad": {+    "verb": "kicad_remove_route",+    "match": "partial"+   },+   "fusion360": null+  },+  {+   "name": "remove_symbol",+   "tier": "core",+   "altium": "altium_remove_symbol",+   "kicad": null,+   "fusion360": null+  },+  {+   "name": "rename_footprint",+   "tier": "core",+   "altium": "altium_rename_footprint",+   "kicad": null,+   "fusion360": null+  },+  {+   "name": "rename_symbol",+   "tier": "core",+   "altium": "altium_rename_symbol",+   "kicad": null,+   "fusion360": null+  },+  {+   "name": "ripup_nets",+   "tier": "core",+   "altium": "altium_ripup_nets",+   "kicad": null,+   "fusion360": {+    "verb": "fusion_ripup_nets",+    "match": "exact"+   }+  },+  {+   "name": "route_net",+   "tier": "core",+   "altium": "altium_route_net",+   "kicad": {+    "verb": "kicad_route_net",+    "match": "exact"+   },+   "fusion360": {+    "verb": "fusion_route_net",+    "match": "exact"+   }+  },+  {+   "name": "routing_state",+   "tier": "core",+   "altium": "altium_routing_state",+   "kicad": {+    "verb": "kicad_routing_state",+    "match": "exact"+   },+   "fusion360": {+    "verb": "fusion_routing_state",+    "match": "exact"+   }+  },+  {+   "name": "rules_by_kind",+   "tier": "core",+   "altium": "altium_rules_by_kind",+   "kicad": null,+   "fusion360": null+  },+  {+   "name": "sch_connect",+   "tier": "core",+   "altium": "altium_sch_connect",+   "kicad": {+    "verb": "kicad_sch_wire / kicad_sch_label",+    "match": "partial"+   },+   "fusion360": null+  },+  {+   "name": "sch_edit",+   "tier": "core",+   "altium": "altium_sch_edit",+   "kicad": {+    "verb": "kicad_sch_set_property / kicad_sch_move / kicad_sch_delete",+    "match": "partial"+   },+   "fusion360": null+  },+  {+   "name": "sch_place_part",+   "tier": "core",+   "altium": "altium_sch_place_part",+   "kicad": {+    "verb": "kicad_sch_place_symbol",+    "match": "exact"+   },+   "fusion360": null+  },+  {+   "name": "sch_sheet_symbol",+   "tier": "core",+   "altium": "altium_sch_sheet_symbol",+   "kicad": null,+   "fusion360": null+  },+  {+   "name": "sch_swap_part",+   "tier": "core",+   "altium": "altium_sch_swap_part",+   "kicad": null,+   "fusion360": null+  },+  {+   "name": "schematic_data",+   "tier": "core",+   "altium": "altium_schematic_data",+   "kicad": null,+   "fusion360": null+  },+  {+   "name": "set_models",+   "tier": "core",+   "altium": "altium_set_models",+   "kicad": null,+   "fusion360": null+  },+  {+   "name": "set_outline",+   "tier": "core",+   "altium": "altium_set_outline",+   "kicad": null,+   "fusion360": null+  },+  {+   "name": "set_params",+   "tier": "core",+   "altium": "altium_set_params",+   "kicad": null,+   "fusion360": null+  },+  {+   "name": "set_track_width",+   "tier": "core",+   "altium": "altium_set_track_width",+   "kicad": null,+   "fusion360": null+  },+  {+   "name": "set_visibility",+   "tier": "core",+   "altium": "altium_set_visibility",+   "kicad": null,+   "fusion360": null+  },+  {+   "name": "sheet_params",+   "tier": "core",+   "altium": "altium_sheet_params",+   "kicad": null,+   "fusion360": null+  },+  {+   "name": "silk_state",+   "tier": "core",+   "altium": "altium_silk_state",+   "kicad": null,+   "fusion360": null+  },+  {+   "name": "swap_footprint",+   "tier": "core",+   "altium": "altium_swap_footprint",+   "kicad": null,+   "fusion360": null+  },+  {+   "name": "symbol_prims",+   "tier": "core",+   "altium": "altium_symbol_prims",+   "kicad": null,+   "fusion360": null+  },+  {+   "name": "xcopy_footprint",+   "tier": "core",+   "altium": "altium_xcopy_footprint",+   "kicad": null,+   "fusion360": null+  },+  {+   "name": "add_rule",+   "tier": "native",+   "altium": "altium_add_rule",+   "kicad": null,+   "fusion360": null+  },+  {+   "name": "annotate",+   "tier": "native",+   "altium": "altium_annotate",+   "kicad": null,+   "fusion360": null+  },+  {+   "name": "apply_pours",+   "tier": "native",+   "altium": "altium_apply_pours",+   "kicad": {+    "verb": "kicad_add_zone / kicad_apply_zone_plan",+    "match": "exact"+   },+   "fusion360": {+    "verb": "fusion_apply_pours",+    "match": "exact"+   }+  },+  {+   "name": "autoroute",+   "tier": "native",+   "altium": "altium_autoroute",+   "kicad": {+    "verb": "kicad_autoroute",+    "match": "exact"+   },+   "fusion360": null+  },+  {+   "name": "build_symbol_modes",+   "tier": "native",+   "altium": "altium_build_symbol_modes",+   "kicad": null,+   "fusion360": null+  },+  {+   "name": "cleanup_libs",+   "tier": "native",+   "altium": "altium_cleanup_libs",+   "kicad": null,+   "fusion360": null+  },+  {+   "name": "compile",+   "tier": "native",+   "altium": "altium_compile",+   "kicad": null,+   "fusion360": null+  },+  {+   "name": "component_classes",+   "tier": "native",+   "altium": "altium_component_classes",+   "kicad": null,+   "fusion360": null+  },+  {+   "name": "diff_pairs",+   "tier": "native",+   "altium": "altium_diff_pairs",+   "kicad": null,+   "fusion360": null+  },+  {+   "name": "export_bom",+   "tier": "native",+   "altium": "altium_export_bom",+   "kicad": {+    "verb": "kicad_export_bom_csv",+    "match": "partial"+   },+   "fusion360": {+    "verb": "fusion_export_bom",+    "match": "exact"+   }+  },+  {+   "name": "export_gerber",+   "tier": "native",+   "altium": "altium_export_gerber",+   "kicad": {+    "verb": "kicad_export_gerber",+    "match": "exact"+   },+   "fusion360": {+    "verb": "fusion_export_gerbers",+    "match": "exact"+   }+  },+  {+   "name": "export_manufacturing",+   "tier": "native",+   "altium": "altium_export_manufacturing",+   "kicad": {+    "verb": "kicad_export_gerber / kicad_export_pos",+    "match": "partial"+   },+   "fusion360": {+    "verb": "fusion_export_gerbers / fusion_export_cpl / fusion_export_bom",+    "match": "partial"+   }+  },+  {+   "name": "export_pdf",+   "tier": "native",+   "altium": "altium_export_pdf",+   "kicad": {+    "verb": "kicad_export_pdf",+    "match": "exact"+   },+   "fusion360": null+  },+  {+   "name": "export_step",+   "tier": "native",+   "altium": "altium_export_step",+   "kicad": {+    "verb": "kicad_export_step",+    "match": "exact"+   },+   "fusion360": {+    "verb": "fusion_export_step",+    "match": "exact"+   }+  },+  {+   "name": "install_library",+   "tier": "native",+   "altium": "altium_install_library",+   "kicad": {+    "verb": "kicad_install_library",+    "match": "exact"+   },+   "fusion360": null+  },+  {+   "name": "install_library_bundle",+   "tier": "native",+   "altium": "altium_install_library_bundle",+   "kicad": {+    "verb": "kicad_install_library_bundle",+    "match": "exact"+   },+   "fusion360": {+    "verb": "fusion_install_library_bundle",+    "match": "exact"+   }+  },+  {+   "name": "list_installed_libraries",+   "tier": "native",+   "altium": "altium_list_installed_libraries",+   "kicad": null,+   "fusion360": null+  },+  {+   "name": "net_classes",+   "tier": "native",+   "altium": "altium_net_classes",+   "kicad": null,+   "fusion360": null+  },+  {+   "name": "netlist",+   "tier": "native",+   "altium": "altium_netlist",+   "kicad": {+    "verb": "kicad_extract_netlist",+    "match": "exact"+   },+   "fusion360": null+  },+  {+   "name": "panelize",+   "tier": "native",+   "altium": "altium_panelize",+   "kicad": null,+   "fusion360": null+  },+  {+   "name": "pcb_report",+   "tier": "native",+   "altium": "altium_pcb_report",+   "kicad": {+    "verb": "kicad_errors",+    "match": "partial"+   },+   "fusion360": null+  },+  {+   "name": "pour_state",+   "tier": "native",+   "altium": "altium_pour_state",+   "kicad": {+    "verb": "kicad_zone_state",+    "match": "exact"+   },+   "fusion360": {+    "verb": "fusion_pour_state",+    "match": "exact"+   }+  },+  {+   "name": "remove_pours",+   "tier": "native",+   "altium": "altium_remove_pours",+   "kicad": {+    "verb": "kicad_remove_zone",+    "match": "exact"+   },+   "fusion360": {+    "verb": "fusion_remove_pours",+    "match": "exact"+   }+  },+  {+   "name": "replicate_layout",+   "tier": "native",+   "altium": "altium_replicate_layout",+   "kicad": null,+   "fusion360": null+  },+  {+   "name": "repour",+   "tier": "native",+   "altium": "altium_repour",+   "kicad": null,+   "fusion360": null+  },+  {+   "name": "routing_drc",+   "tier": "native",+   "altium": "altium_routing_drc",+   "kicad": {+    "verb": "kicad_routing_validate",+    "match": "partial"+   },+   "fusion360": {+    "verb": "fusion_routing_drc",+    "match": "exact"+   }+  },+  {+   "name": "run_drc",+   "tier": "native",+   "altium": "altium_run_drc",+   "kicad": {+    "verb": "kicad_run_drc",+    "match": "exact"+   },+   "fusion360": null+  },+  {+   "name": "run_erc",+   "tier": "native",+   "altium": "altium_run_erc",+   "kicad": {+    "verb": "kicad_run_erc",+    "match": "exact"+   },+   "fusion360": null+  },+  {+   "name": "set_design_rules",+   "tier": "native",+   "altium": "altium_set_design_rules",+   "kicad": {+    "verb": "kicad_set_design_rules",+    "match": "exact"+   },+   "fusion360": {+    "verb": "fusion_set_design_rules",+    "match": "exact"+   }+  },+  {+   "name": "silkscreen_prep",+   "tier": "native",+   "altium": "altium_silkscreen_prep",+   "kicad": null,+   "fusion360": null+  },+  {+   "name": "symbol_mode",+   "tier": "native",+   "altium": "altium_symbol_mode",+   "kicad": null,+   "fusion360": null+  },+  {+   "name": "teardrops",+   "tier": "native",+   "altium": "altium_teardrops",+   "kicad": null,+   "fusion360": null+  },+  {+   "name": "uninstall_library",+   "tier": "native",+   "altium": "altium_uninstall_library",+   "kicad": {+    "verb": "kicad_uninstall_library",+    "match": "exact"+   },+   "fusion360": null+  },+  {+   "name": "update_pcb",+   "tier": "native",+   "altium": "altium_update_pcb",+   "kicad": null,+   "fusion360": null+  },+  {+   "name": "assembly_check",+   "tier": "checks",+   "altium": "altium_assembly_check",+   "kicad": null,+   "fusion360": null+  },+  {+   "name": "auto_classes",+   "tier": "checks",+   "altium": "altium_auto_classes",+   "kicad": null,+   "fusion360": null+  },+  {+   "name": "board_features",+   "tier": "checks",+   "altium": "altium_board_features",+   "kicad": null,+   "fusion360": null+  },+  {+   "name": "board_report",+   "tier": "checks",+   "altium": "altium_board_report",+   "kicad": null,+   "fusion360": null+  },+  {+   "name": "bom",+   "tier": "checks",+   "altium": "altium_bom",+   "kicad": null,+   "fusion360": null+  },+  {+   "name": "build_board",+   "tier": "checks",+   "altium": "altium_build_board",+   "kicad": null,+   "fusion360": null+  },+  {+   "name": "build_footprint",+   "tier": "checks",+   "altium": "altium_build_footprint",+   "kicad": null,+   "fusion360": null+  },+  {+   "name": "build_schematic",+   "tier": "checks",+   "altium": "altium_build_schematic",+   "kicad": null,+   "fusion360": {+    "verb": "fusion_schematic_plan / fusion_schematic_run",+    "match": "partial"+   }+  },+  {+   "name": "check_orientation",+   "tier": "checks",+   "altium": "altium_check_orientation",+   "kicad": null,+   "fusion360": null+  },+  {+   "name": "copper_balance",+   "tier": "checks",+   "altium": "altium_copper_balance",+   "kicad": null,+   "fusion360": null+  },+  {+   "name": "cross_check",+   "tier": "checks",+   "altium": "altium_cross_check",+   "kicad": {+    "verb": "kicad_analyze_connections",+    "match": "partial"+   },+   "fusion360": null+  },+  {+   "name": "current_capacity",+   "tier": "checks",+   "altium": "altium_current_capacity",+   "kicad": null,+   "fusion360": null+  },+  {+   "name": "fab_check",+   "tier": "checks",+   "altium": "altium_fab_check",+   "kicad": null,+   "fusion360": null+  },+  {+   "name": "find_space",+   "tier": "checks",+   "altium": "altium_find_space",+   "kicad": null,+   "fusion360": null+  },+  {+   "name": "footprint_from_land_pattern",+   "tier": "checks",+   "altium": "altium_footprint_from_land_pattern",+   "kicad": null,+   "fusion360": null+  },+  {+   "name": "height_check",+   "tier": "checks",+   "altium": "altium_height_check",+   "kicad": null,+   "fusion360": null+  },+  {+   "name": "impedance",+   "tier": "checks",+   "altium": "altium_impedance",+   "kicad": null,+   "fusion360": null+  },+  {+   "name": "import_part",+   "tier": "checks",+   "altium": "altium_import_part",+   "kicad": null,+   "fusion360": null+  },+  {+   "name": "jlcpcb_order",+   "tier": "checks",+   "altium": "altium_jlcpcb_order",+   "kicad": null,+   "fusion360": null+  },+  {+   "name": "length_tune",+   "tier": "checks",+   "altium": "altium_length_tune",+   "kicad": null,+   "fusion360": null+  },+  {+   "name": "net_lengths",+   "tier": "checks",+   "altium": "altium_net_lengths",+   "kicad": null,+   "fusion360": null+  },+  {+   "name": "pin_pad_check",+   "tier": "checks",+   "altium": "altium_pin_pad_check",+   "kicad": null,+   "fusion360": null+  },+  {+   "name": "placement_check",+   "tier": "checks",+   "altium": "altium_placement_check",+   "kicad": {+    "verb": "kicad_placement_validate",+    "match": "partial"+   },+   "fusion360": null+  },+  {+   "name": "read_sheet_file",+   "tier": "checks",+   "altium": "altium_read_sheet_file",+   "kicad": null,+   "fusion360": null+  },+  {+   "name": "return_path",+   "tier": "checks",+   "altium": "altium_return_path",+   "kicad": null,+   "fusion360": null+  },+  {+   "name": "review_board",+   "tier": "checks",+   "altium": "altium_review_board",+   "kicad": {+    "verb": "kicad_lint_board",+    "match": "partial"+   },+   "fusion360": null+  },+  {+   "name": "route_pair",+   "tier": "checks",+   "altium": "altium_route_pair",+   "kicad": null,+   "fusion360": null+  },+  {+   "name": "schematic_review",+   "tier": "checks",+   "altium": "altium_schematic_review",+   "kicad": {+    "verb": "kicad_lint_schematic",+    "match": "partial"+   },+   "fusion360": null+  },+  {+   "name": "silk_designators",+   "tier": "checks",+   "altium": "altium_silk_designators",+   "kicad": null,+   "fusion360": null+  },+  {+   "name": "test_board",+   "tier": "checks",+   "altium": "altium_test_board",+   "kicad": null,+   "fusion360": null+  },+  {+   "name": "thermal_vias",+   "tier": "checks",+   "altium": "altium_thermal_vias",+   "kicad": null,+   "fusion360": null+  },+  {+   "name": "via_fence",+   "tier": "checks",+   "altium": "altium_via_fence",+   "kicad": null,+   "fusion360": null+  },+  {+   "name": "activate_document",+   "tier": "app",+   "altium": "altium_activate_document",+   "kicad": null,+   "fusion360": {+    "verb": "fusion_activate_document",+    "match": "exact"+   }+  },+  {+   "name": "agent_recover",+   "tier": "app",+   "altium": "altium_agent_recover",+   "kicad": null,+   "fusion360": null+  },+  {+   "name": "agent_start",+   "tier": "app",+   "altium": "altium_agent_start",+   "kicad": null,+   "fusion360": null+  },+  {+   "name": "agent_status",+   "tier": "app",+   "altium": "altium_agent_status",+   "kicad": null,+   "fusion360": null+  },+  {+   "name": "agent_stop",+   "tier": "app",+   "altium": "altium_agent_stop",+   "kicad": null,+   "fusion360": null+  },+  {+   "name": "animate_view",+   "tier": "app",+   "altium": "altium_animate_view",+   "kicad": null,+   "fusion360": null+  },+  {+   "name": "check_dialogs",+   "tier": "app",+   "altium": "altium_check_dialogs",+   "kicad": null,+   "fusion360": null+  },+  {+   "name": "close",+   "tier": "app",+   "altium": "altium_close",+   "kicad": {+    "verb": "kicad_close",+    "match": "exact"+   },+   "fusion360": {+    "verb": "fusion_stop",+    "match": "exact"+   }+  },+  {+   "name": "close_document",+   "tier": "app",+   "altium": "altium_close_document",+   "kicad": null,+   "fusion360": {+    "verb": "fusion_close_document",+    "match": "exact"+   }+  },+  {+   "name": "connect_workspace",+   "tier": "app",+   "altium": "altium_connect_workspace",+   "kicad": null,+   "fusion360": null+  },+  {+   "name": "demo",+   "tier": "app",+   "altium": "altium_demo",+   "kicad": {+    "verb": "kicad_demo",+    "match": "exact"+   },+   "fusion360": {+    "verb": "fusion_demo",+    "match": "exact"+   }+  },+  {+   "name": "describe",+   "tier": "app",+   "altium": "altium_describe",+   "kicad": {+    "verb": "kicad_describe",+    "match": "exact"+   },+   "fusion360": {+    "verb": "fusion_describe",+    "match": "exact"+   }+  },+  {+   "name": "diagnostics",+   "tier": "app",+   "altium": "altium_diagnostics",+   "kicad": {+    "verb": "kicad_diagnostics",+    "match": "exact"+   },+   "fusion360": null+  },+  {+   "name": "dialog_log",+   "tier": "app",+   "altium": "altium_dialog_log",+   "kicad": null,+   "fusion360": null+  },+  {+   "name": "dismiss_dialogs",+   "tier": "app",+   "altium": "altium_dismiss_dialogs",+   "kicad": {+    "verb": "kicad_dismiss_dialogs",+    "match": "exact"+   },+   "fusion360": {+    "verb": "fusion_dismiss_blocking_dialogs",+    "match": "exact"+   }+  },+  {+   "name": "ensure_licensed",+   "tier": "app",+   "altium": "altium_ensure_licensed",+   "kicad": null,+   "fusion360": null+  },+  {+   "name": "eval",+   "tier": "app",+   "altium": "altium_eval",+   "kicad": null,+   "fusion360": null+  },+  {+   "name": "extension_ping",+   "tier": "app",+   "altium": "altium_extension_ping",+   "kicad": null,+   "fusion360": null+  },+  {+   "name": "foreground",+   "tier": "app",+   "altium": "altium_foreground",+   "kicad": null,+   "fusion360": null+  },+  {+   "name": "install",+   "tier": "app",+   "altium": "altium_install",+   "kicad": null,+   "fusion360": {+    "verb": "fusion_install_fusion",+    "match": "exact"+   }+  },+  {+   "name": "install_extension",+   "tier": "app",+   "altium": "altium_install_extension",+   "kicad": null,+   "fusion360": null+  },+  {+   "name": "install_receipt",+   "tier": "app",+   "altium": "altium_install_receipt",+   "kicad": null,+   "fusion360": null+  },+  {+   "name": "instances",+   "tier": "app",+   "altium": "altium_instances",+   "kicad": null,+   "fusion360": null+  },+  {+   "name": "launch",+   "tier": "app",+   "altium": "altium_launch",+   "kicad": {+    "verb": "kicad_launch",+    "match": "exact"+   },+   "fusion360": {+    "verb": "fusion_start",+    "match": "exact"+   }+  },+  {+   "name": "license",+   "tier": "app",+   "altium": "altium_license",+   "kicad": null,+   "fusion360": null+  },+  {+   "name": "license_status",+   "tier": "app",+   "altium": "altium_license_status",+   "kicad": null,+   "fusion360": null+  },+  {+   "name": "list_commands",+   "tier": "app",+   "altium": "altium_list_commands",+   "kicad": null,+   "fusion360": null+  },+  {+   "name": "open_document",+   "tier": "app",+   "altium": "altium_open_document",+   "kicad": null,+   "fusion360": null+  },+  {+   "name": "open_project",+   "tier": "app",+   "altium": "altium_open_project",+   "kicad": null,+   "fusion360": null+  },+  {+   "name": "panel",+   "tier": "app",+   "altium": "altium_panel",+   "kicad": null,+   "fusion360": null+  },+  {+   "name": "progress",+   "tier": "app",+   "altium": "altium_progress",+   "kicad": {+    "verb": "kicad_progress",+    "match": "exact"+   },+   "fusion360": null+  },+  {+   "name": "readiness",+   "tier": "app",+   "altium": "altium_readiness",+   "kicad": {+    "verb": "kicad_readiness",+    "match": "exact"+   },+   "fusion360": {+    "verb": "fusion_readiness",+    "match": "exact"+   }+  },+  {+   "name": "run_command",+   "tier": "app",+   "altium": "altium_run_command",+   "kicad": null,+   "fusion360": null+  },+  {+   "name": "run_script",+   "tier": "app",+   "altium": "altium_run_script",+   "kicad": null,+   "fusion360": null+  },+  {+   "name": "save",+   "tier": "app",+   "altium": "altium_save",+   "kicad": null,+   "fusion360": null+  },+  {+   "name": "sch_zoom",+   "tier": "app",+   "altium": "altium_sch_zoom",+   "kicad": null,+   "fusion360": null+  },+  {+   "name": "select_net",+   "tier": "app",+   "altium": "altium_select_net",+   "kicad": {+    "verb": "kicad_select_net",+    "match": "exact"+   },+   "fusion360": null+  },+  {+   "name": "selection",+   "tier": "app",+   "altium": "altium_selection",+   "kicad": null,+   "fusion360": null+  },+  {+   "name": "selftest_script_error",+   "tier": "app",+   "altium": "altium_selftest_script_error",+   "kicad": null,+   "fusion360": null+  },+  {+   "name": "set_layer_visibility",+   "tier": "app",+   "altium": "altium_set_layer_visibility",+   "kicad": null,+   "fusion360": null+  },+  {+   "name": "setup_trial",+   "tier": "app",+   "altium": "altium_setup_trial",+   "kicad": null,+   "fusion360": null+  },+  {+   "name": "show_2d_board",+   "tier": "app",+   "altium": "altium_show_2d_board",+   "kicad": {+    "verb": "kicad_show_2d_board",+    "match": "exact"+   },+   "fusion360": {+    "verb": "fusion_show_2d_board",+    "match": "exact"+   }+  },+  {+   "name": "show_3d_board",+   "tier": "app",+   "altium": "altium_show_3d_board",+   "kicad": {+    "verb": "kicad_show_3d_board",+    "match": "exact"+   },+   "fusion360": {+    "verb": "fusion_show_3d_board",+    "match": "exact"+   }+  },+  {+   "name": "show_3d_chip",+   "tier": "app",+   "altium": "altium_show_3d_chip",+   "kicad": {+    "verb": "kicad_show_3d_chip",+    "match": "exact"+   },+   "fusion360": null+  },+  {+   "name": "show_footprint",+   "tier": "app",+   "altium": "altium_show_footprint",+   "kicad": {+    "verb": "kicad_show_footprint",+    "match": "exact"+   },+   "fusion360": {+    "verb": "fusion_show_footprint",+    "match": "exact"+   }+  },+  {+   "name": "show_library",+   "tier": "app",+   "altium": "altium_show_library",+   "kicad": {+    "verb": "kicad_show_library",+    "match": "exact"+   },+   "fusion360": {+    "verb": "fusion_show_library",+    "match": "exact"+   }+  },+  {+   "name": "show_project",+   "tier": "app",+   "altium": "altium_show_project",+   "kicad": {+    "verb": "kicad_show_project",+    "match": "exact"+   },+   "fusion360": {+    "verb": "fusion_show_project",+    "match": "exact"+   }+  },+  {+   "name": "show_schematic",+   "tier": "app",+   "altium": "altium_show_schematic",+   "kicad": {+    "verb": "kicad_show_schematic",+    "match": "exact"+   },+   "fusion360": {+    "verb": "fusion_show_schematic",+    "match": "exact"+   }+  },+  {+   "name": "show_symbol",+   "tier": "app",+   "altium": "altium_show_symbol",+   "kicad": {+    "verb": "kicad_show_symbol",+    "match": "exact"+   },+   "fusion360": {+    "verb": "fusion_show_symbol",+    "match": "exact"+   }+  },+  {+   "name": "signin",+   "tier": "app",+   "altium": "altium_signin",+   "kicad": null,+   "fusion360": {+    "verb": "fusion_signin",+    "match": "exact"+   }+  },+  {+   "name": "signin_status",+   "tier": "app",+   "altium": "altium_signin_status",+   "kicad": null,+   "fusion360": null+  },+  {+   "name": "state",+   "tier": "app",+   "altium": "altium_state",+   "kicad": {+    "verb": "kicad_state",+    "match": "exact"+   },+   "fusion360": {+    "verb": "fusion_state",+    "match": "exact"+   }+  },+  {+   "name": "status",+   "tier": "app",+   "altium": "altium_status",+   "kicad": {+    "verb": "kicad_status",+    "match": "exact"+   },+   "fusion360": null+  },+  {+   "name": "uninstall",+   "tier": "app",+   "altium": "altium_uninstall",+   "kicad": {+    "verb": "kicad_uninstall",+    "match": "exact"+   },+   "fusion360": {+    "verb": "fusion_uninstall",+    "match": "exact"+   }+  },+  {+   "name": "window_info",+   "tier": "app",+   "altium": "altium_window_info",+   "kicad": {+    "verb": "kicad_window_info",+    "match": "exact"+   },+   "fusion360": null+  },+  {+   "name": "zoom_to",+   "tier": "app",+   "altium": "altium_zoom_to",+   "kicad": null,+   "fusion360": null+  },+  {+   "name": "a365_configure",+   "tier": "platform",+   "altium": "altium_a365_configure",+   "kicad": null,+   "fusion360": null+  },+  {+   "name": "a365_files_upload",+   "tier": "platform",+   "altium": "altium_a365_files_upload",+   "kicad": null,+   "fusion360": null+  },+  {+   "name": "a365_library_overview",+   "tier": "platform",+   "altium": "altium_a365_library_overview",+   "kicad": null,+   "fusion360": null+  },+  {+   "name": "a365_library_schema",+   "tier": "platform",+   "altium": "altium_a365_library_schema",+   "kicad": null,+   "fusion360": null+  },+  {+   "name": "a365_logout",+   "tier": "platform",+   "altium": "altium_a365_logout",+   "kicad": null,+   "fusion360": null+  },+  {+   "name": "a365_oauth_begin",+   "tier": "platform",+   "altium": "altium_a365_oauth_begin",+   "kicad": null,+   "fusion360": null+  },+  {+   "name": "a365_oauth_cancel",+   "tier": "platform",+   "altium": "altium_a365_oauth_cancel",+   "kicad": null,+   "fusion360": null+  },+  {+   "name": "a365_oauth_status",+   "tier": "platform",+   "altium": "altium_a365_oauth_status",+   "kicad": null,+   "fusion360": null+  },+  {+   "name": "a365_query",+   "tier": "platform",+   "altium": "altium_a365_query",+   "kicad": null,+   "fusion360": null+  },+  {+   "name": "a365_refresh",+   "tier": "platform",+   "altium": "altium_a365_refresh",+   "kicad": null,+   "fusion360": null+  },+  {+   "name": "a365_status",+   "tier": "platform",+   "altium": "altium_a365_status",+   "kicad": null,+   "fusion360": null+  },+  {+   "name": "compile_intlib",+   "tier": "platform",+   "altium": "altium_compile_intlib",+   "kicad": null,+   "fusion360": null+  },+  {+   "name": "extract_intlib",+   "tier": "platform",+   "altium": "altium_extract_intlib",+   "kicad": null,+   "fusion360": null+  },+  {+   "name": "import_kicad",+   "tier": "platform",+   "altium": "altium_import_kicad",+   "kicad": null,+   "fusion360": null+  },+  {+   "name": "outjob_info",+   "tier": "platform",+   "altium": "altium_outjob_info",+   "kicad": null,+   "fusion360": null+  },+  {+   "name": "run_outjob",+   "tier": "platform",+   "altium": "altium_run_outjob",+   "kicad": null,+   "fusion360": null+  },+  {+   "name": "variants",+   "tier": "platform",+   "altium": "altium_variants",+   "kicad": null,+   "fusion360": null+  },+  {+   "name": "workspace_place",+   "tier": "platform",+   "altium": "altium_workspace_place",+   "kicad": null,+   "fusion360": null+  },+  {+   "name": "workspace_search",+   "tier": "platform",+   "altium": "altium_workspace_search",+   "kicad": null,+   "fusion360": null+  }+ ]+}
verbs/standard.jsonadded+6780
@@ -0,0 +1,6780 @@+{+ "reference": {+  "bridge": "altium",+  "version": "0.2.75"+ },+ "compared": {+  "kicad": "1.0.58",+  "fusion360": "1.11.44"+ },+ "tiers": {+  "core": "Read and edit the design. Every bridge provides these: they are how an AI sees and changes a board.",+  "native": "Wraps the EDA tool's own feature (its DRC, ERC, Gerbers, pours, Update PCB). Every tool has one; the bridge exposes it under this name.",+  "checks": "Engineering computed from the board model, not by the tool: one shared engine, so every bridge that serves the model gets them all.",+  "app": "Driving the application itself (launch, licence, dialogs, windows, views). Each bridge has its own; the names are shared where the job is.",+  "platform": "Specific to one tool's platform (Altium 365, OutJobs, integrated libraries). Not part of the standard; listed so nothing is lost."+ },+ "verbs": [+  {+   "name": "add_body",+   "tier": "core",+   "summary": "Put a 3D body on a footprint of a PcbLib: a STEP model, or an extruded box when no step is given. It REPLACES the footprint's existing 3D bodies (a re-import never stacks a second model) unless keepExisting:true. The bridge then re-aligns bodies onto the pads (normalize_bodies) and returns a frame.",+   "input": {+    "bhMil": "int, default 100 - box depth",+    "bwMil": "int, default 100 - box width",+    "footprintName": "string - required when the library holds more than one footprint",+    "hMil": "int, default 40 - box height",+    "keepExisting": "bool, default false - ADD next to the existing bodies instead of replacing them (a deliberate second body)",+    "libraryPath": "path to a .PcbLib (required)",+    "soMil": "int, default 0 - standoff",+    "step": "Windows path to a .step - optional",+    "timeout": "seconds, default 90"+   },+   "output": {+    "_hint": "string - what was replaced, and a WARNING when bodies are still stacked",+    "data": "{footprint, body, replacedBodies, keptExisting, bodiesBefore, sameNameFootprints, bodiesNow, bodiesOnCanvas}",+    "normalized": "string - the shifts applied",+    "replacedBodies": "int - old bodies removed before the add",+    "shot": "{image: base64 png, width, height}"+   },+   "hint": "altium_save {filePath: libraryPath}, then altium_show_3d_chip to look at it; boards that already place the part need PCB:UpdateFootprints. Never engrave the MPN into the STEP.",+   "pitfalls": [+    "It REPLACES by default (from 0.2.52): data.replacedBodies says how many old bodies went and data.bodiesNow should read 1. keepExisting:true is the only way to stack a second body.",+    "A bad step path ('could not load model') removes nothing: the old body stays.",+    "data.sameNameFootprints > 1 = several footprints share the name and only the FIRST got the body: altium_remove_footprint the stale copies.",+    "Without footprintName it acts on the only footprint in the library (name_required when there are several).",+    "'could not load model' = the STEP path is not on THIS machine: stage it with send_files."+   ],+   "related": [+    "normalize_bodies",+    "show_3d_chip",+    "pcb_report",+    "import_part",+    "footprint_primitives"+   ],+   "example": {+    "footprintName": "VL53L5CX",+    "libraryPath": "C:/lib/Adom_ToF.PcbLib",+    "step": "C:/lib/VL53L5CX.step"+   },+   "longRunning": false,+   "timeoutSeconds": 90+  },+  {+   "name": "add_models",+   "tier": "core",+   "summary": "Attach one or more PCBLIB footprint models, parameters, comment and description to a SchLib component (Altium's several-footprints-per-symbol mechanism).",+   "input": {+    "comment": "string",+    "description": "string",+    "libraryPath": "path to a .SchLib (required)",+    "models": "[{name, pcblib, description}]",+    "params": "{key: value}",+    "symbolName": "string (required)",+    "timeout": "seconds, default 120"+   },+   "output": {+    "data": "{libref, models: int, params: int}",+    "shot": "{image: base64 png, width, height}"+   },+   "hint": "Check data.models is what you sent, then altium_save. To REPLACE the footprint use altium_set_models.",+   "pitfalls": [+    "NOT idempotent: every call APPENDS another implementation. Repair = altium_set_models, or altium_remove_symbol + re-import.",+    "';' and '|' inside a description are rewritten (they are the spec separators).",+    "altium_read_symbol does not report models; use altium_symbol_prims {models:true}."+   ],+   "related": [+    "set_models",+    "set_params",+    "symbol_prims",+    "remove_symbol"+   ],+   "example": {+    "libraryPath": "C:/lib/Adom_ToF.SchLib",+    "models": [+     {+      "description": "LGA-16 6.4x3.0",+      "name": "VL53L5CX",+      "pcblib": "C:/lib/Adom_ToF.PcbLib"+     }+    ],+    "params": {+     "Manufacturer Part Number": "VL53L5CXV0GC/1"+    },+    "symbolName": "VL53L5CX"+   },+   "longRunning": false,+   "timeoutSeconds": 120+  },+  {+   "name": "add_symbol_art",+   "tier": "core",+   "summary": "Draw the chip-outline artwork (the chip-thumbnailer polylines adom-symbol baked into the KiCad symbol) into a SchLib component, fitted to its body rectangle, so the Altium symbol shows the same chip as KiCad and Fusion.",+   "input": {+    "clear": "bool, default true: remove the component's existing polylines/lines first",+    "kicadSym": "Windows path to the .kicad_sym that carries the baked outline (stage it with send_files)",+    "libraryPath": "path (.SchLib)",+    "mode": "lines (default) | polyline",+    "symbolName": "string (LibReference)"+   },+   "output": {+    "bodyRectMil": "[x0,y0,x1,y1]",+    "points": "int",+    "polylines": "int"+   },+   "hint": "Run once per part right after assemble-altium / build_symbol_modes, then altium_save the SchLib and altium_show_symbol to check.",+   "pitfalls": [+    "clear:true (default) also removes hand-drawn lines on the symbol; only the pins and the body rectangle survive.",+    "The KiCad symbol must already carry the outline (adom-symbol create bakes it; --outline-name none for side-etched chips)."+   ],+   "related": [+    "show_symbol",+    "symbol_prims",+    "save"+   ],+   "example": {+    "kicadSym": "C:/lib/VL53L5CX.kicad_sym",+    "libraryPath": "C:/lib/Adom_ToF.SchLib",+    "symbolName": "VL53L5CX"+   },+   "longRunning": false,+   "timeoutSeconds": 180+  },+  {+   "name": "apply_silkscreen",+   "tier": "core",+   "summary": "Land a silkscreen plan in one undo step: designators and comments moved, sized and turned (or hidden), free overlay texts and lines added (bottom texts mirrored), earlier ones removed; answers silk_state after.",+   "input": {+    "comments": "[same] - the value",+    "lines": "[{x1, y1, x2, y2, width?: mm (0.15), side?}]",+    "names": "[{ref, x, y, size, rotation?, show?}] - the designator's own location, mm",+    "remove": "{texts:[raw], lines:[raw]} - silk_state raw handles of earlier free items",+    "texts": "[{text, x, y, size, rotation?, side?: top|bottom}]"+   },+   "output": {+    "after": "silk_state",+    "applied": "int",+    "failed": "int",+    "results": "[ok|err:...]"+   },+   "hint": "A text's location is its lower left before rotation (for 90 degrees: the box's lower right). Stroke width is a seventh of the height.",+   "pitfalls": [+    "Needs the board current.",+    "Removal matches the raw handle exactly: pass what silk_state answered."+   ],+   "related": [+    "silk_state",+    "routing_drc"+   ],+   "example": {+    "lines": [+     {+      "x1": 1,+      "x2": 5,+      "y1": 1,+      "y2": 1+     }+    ],+    "names": [+     {+      "ref": "U1",+      "size": 0.8,+      "x": 12.0,+      "y": 14.2+     }+    ]+   },+   "longRunning": false,+   "timeoutSeconds": 600+  },+  {+   "name": "board_outline",+   "tier": "core",+   "summary": "Draw the board shape as tracks and arcs on a mechanical layer: the outline a fab house reads from the Gerbers. A board built by script has its shape but nothing on an outline layer, so its GM1/GKO Gerbers are empty.",+   "input": {+    "filePath": "path to the .PcbDoc - optional",+    "mech": "mechanical layer number, default 1",+    "replace": "bool - draw even when the layer already holds primitives",+    "widthMm": "line width, default 0.1"+   },+   "output": {+    "alreadyThere": "primitives the layer held",+    "drawn": "segments drawn",+    "layer": "string"+   },+   "hint": "altium_export_manufacturing checks the outline Gerbers and calls this itself when they are empty (addOutline:false stops it). altium_save keeps the outline.",+   "pitfalls": [+    "A layer that already holds anything is left alone unless replace:true (it may be the outline already, drawn by hand)."+   ],+   "related": [+    "export_manufacturing",+    "set_outline",+    "board_shape"+   ],+   "example": {+    "mech": 1,+    "widthMm": 0.1+   },+   "longRunning": false,+   "timeoutSeconds": 60+  },+  {+   "name": "board_shape",+   "tier": "core",+   "summary": "Redefine the CURRENT board's outline as a wMm x hMm rectangle at (x0Mm, y0Mm) by rewriting the board outline's segments.",+   "input": {+    "hMm": "number, default 15",+    "mechOutline": "bool - also draw the rectangle as 0.1 mm tracks on Mechanical 1 so the fab Gerber (.GM1) has an outline",+    "pcbPath": "path to a .PcbDoc - optional; opened first",+    "timeout": "seconds, default 120",+    "wMm": "number, default 15",+    "x0Mm": "number, default 0",+    "y0Mm": "number, default 0"+   },+   "output": {+    "data": "{outlineMil: [left, bottom, right, top] read from the outline vertices, outlineSource, mechOutlineTracks}",+    "roundtripMs": "int"+   },+   "hint": "Then altium_save {filePath: pcbPath} and altium_show_2d_board {filePath} to see it. altium_pcb_report {want:'board'} reads outlineMil back.",+   "pitfalls": [+    "errorCode no_board_outline: the current document is a PcbLib (its internal board has no outline) - pass pcbPath.",+    "bad_size = wMm/hMm not greater than 0; bad_number = a value was not numeric.",+    "It REPLACES the outline; the previous shape is not kept.",+    "The board shape alone is NOT plotted into any Gerber: pass mechOutline:true (once, at the final size: each call adds a new set of tracks)."+   ],+   "related": [+    "test_board",+    "pcb_report",+    "show_2d_board",+    "save"+   ],+   "example": {+    "hMm": 14,+    "pcbPath": "C:/proj/tof.PcbDoc",+    "wMm": 39+   },+   "longRunning": false,+   "timeoutSeconds": 120+  },+  {+   "name": "board_summary",+   "tier": "core",+   "summary": "One-screen summary of the current board, shaped from one read_board round trip: component counts by side, nets, copper layers actually in use, rule counts.",+   "input": {+    "limit": "int, default 2000",+    "raw": "bool - return the unshaped agent payload",+    "sections": "string csv - optional"+   },+   "output": {+    "roundtripMs": "int",+    "summary": "{board, components, componentsTop, componentsBottom, nets, copperLayersInUse, copperLayersInUseNames, layersWithPrimitives, copperLayersAddressable, rules, rulesEnabled}"+   },+   "hint": "Start here on an unknown board, then altium_review_board for findings or altium_component_data / altium_net_connections for detail.",+   "pitfalls": [+    "Needs a .PcbDoc current (altium_activate_document).",+    "copperLayersAddressable is what Altium CAN address (top + 30 mid + 16 planes + bottom), not this board's stack: report copperLayersInUse."+   ],+   "related": [+    "read_board",+    "layers_in_use",+    "rules_by_kind",+    "review_board"+   ],+   "example": {},+   "longRunning": false,+   "timeoutSeconds": 120+  },+  {+   "name": "clone_symbol",+   "tier": "core",+   "summary": "Copy a symbol inside the current SchLib under a new name, all display modes included.",+   "input": {+    "from": "string - existing LibReference",+    "libraryPath": "path to a .SchLib - optional; opened first",+    "timeout": "seconds, default 90",+    "to": "string - new LibReference"+   },+   "output": {+    "data": "{from, to, primitives: int, displayModeCount}",+    "roundtripMs": "int"+   },+   "hint": "Then altium_set_params on the clone (its own MPN and comment) and altium_save.",+   "pitfalls": [+    "'target already exists' - pick another name or altium_remove_symbol first.",+    "Same library only; there is no cross-library symbol copy."+   ],+   "related": [+    "rename_symbol",+    "remove_symbol",+    "set_params",+    "save"+   ],+   "example": {+    "from": "VL53L5CX",+    "libraryPath": "C:/lib/Adom_ToF.SchLib",+    "to": "VL53L7CX"+   },+   "longRunning": false,+   "timeoutSeconds": 90+  },+  {+   "name": "component_data",+   "tier": "core",+   "summary": "Placement data for one board component, or for all of them: footprint pattern, comment, layer/side, position, rotation, size, pad count and the nets on its pads.",+   "input": {+    "designator": "string - optional; omit for every component",+    "limit": "int, default 500"+   },+   "output": {+    "data": "{component: {designator, pattern, comment, layerId, bottom, xMil, yMil, rotation, wMil, hMil, pads, nets}} | {count, components: [..]}",+    "roundtripMs": "int"+   },+   "hint": "Then altium_component_pins {designator} for pad-level nets, or altium_footprint_primitives {designator} for the geometry.",+   "pitfalls": [+    "Needs a .PcbDoc current.",+    "Units are mils; the designator match ignores case. limit only applies to the all-components form."+   ],+   "related": [+    "list_designators",+    "component_pins",+    "footprint_primitives"+   ],+   "example": {+    "designator": "U1"+   },+   "longRunning": false,+   "timeoutSeconds": 90+  },+  {+   "name": "component_pins",+   "tier": "core",+   "summary": "Every pad of one board component with its net, position, layer, size and hole.",+   "input": {+    "designator": "string (required), e.g. U1"+   },+   "output": {+    "data": "{designator, count, pins: [{name, net, xMil, yMil, layerId, wMil, hMil, holeMil}]}",+    "roundtripMs": "int"+   },+   "hint": "Use pins[].net with altium_net_connections {net} to follow a signal across the board.",+   "pitfalls": [+    "Needs a .PcbDoc current.",+    "holeMil 0 = an SMD pad."+   ],+   "related": [+    "component_data",+    "net_connections",+    "footprint_primitives"+   ],+   "example": {+    "designator": "U1"+   },+   "longRunning": false,+   "timeoutSeconds": 90+  },+  {+   "name": "copper_layers",+   "tier": "core",+   "summary": "Every copper layer Altium can address on the current board (top, mid 1-30, planes 1-16, bottom) with names and visibility.",+   "input": {},+   "output": {+    "data": "{layers: [{name, id, kind, displayed}]}",+    "roundtripMs": "int"+   },+   "hint": "Do not count these as the board's layers: altium_layers_in_use says which carry copper.",+   "pitfalls": [+    "Needs a .PcbDoc current.",+    "A capability list (the same on any board), not the stack in use."+   ],+   "related": [+    "layers",+    "layers_in_use"+   ],+   "example": {},+   "longRunning": false,+   "timeoutSeconds": 90+  },+  {+   "name": "copy_footprint",+   "tier": "core",+   "summary": "Duplicate a footprint inside the current PcbLib under a new name (every primitive replicated).",+   "input": {+    "from": "string",+    "libraryPath": "path to a .PcbLib - optional; opened first",+    "timeout": "seconds, default 90",+    "to": "string"+   },+   "output": {+    "data": "{from, to, primitives: int}",+    "roundtripMs": "int"+   },+   "hint": "For a footprint from ANOTHER library use altium_xcopy_footprint. Then altium_save.",+   "pitfalls": [+    "'target already exists' - choose another name or altium_remove_footprint first."+   ],+   "related": [+    "xcopy_footprint",+    "rename_footprint",+    "pcb_report"+   ],+   "example": {+    "from": "VL53L5CX",+    "libraryPath": "C:/lib/Adom_ToF.PcbLib",+    "to": "VL53L5CX_HANDSOLDER"+   },+   "longRunning": false,+   "timeoutSeconds": 90+  },+  {+   "name": "create_footprint",+   "tier": "core",+   "summary": "Create one footprint from a pad list in a PcbLib (the library is created and saved if the path does not exist) and return a proof frame.",+   "input": {+    "description": "string",+    "footprints": "[{name, pads, ...}] - optional: make several in one call (each over the shared args)",+    "libraryPath": "path to a .PcbLib (required)",+    "name": "string (required)",+    "pads": "'des,x_mil,y_mil,w_mil,h_mil,rect|round,hole_mil;...' - hole 0 = SMD",+    "timeout": "seconds, default 90"+   },+   "output": {+    "data": "{footprint, library, libraryState: opened|created, pads: int}",+    "shot": "{image: base64 png, width, height}",+    "windowEtiquette": "background"+   },+   "hint": "The footprint is registered but the library is NOT saved: altium_save {filePath: libraryPath}. Then altium_add_body for 3D and altium_show_footprint to check. For a real part use altium_build_footprint (spec with silkscreen and courtyard).",+   "pitfalls": [+    "errorCode bad_number = a pad field that must be a number was not; nothing is created.",+    "Units are MILS here; altium_build_footprint specs are millimetres.",+    "A fresh library carries the stray PCBComponent_1: altium_cleanup_libs."+   ],+   "related": [+    "build_footprint",+    "add_body",+    "save",+    "show_footprint"+   ],+   "example": {+    "libraryPath": "C:/lib/Test.PcbLib",+    "name": "R0603",+    "pads": "1,-30,0,35,37,rect,0;2,30,0,35,37,rect,0"+   },+   "longRunning": false,+   "timeoutSeconds": 90+  },+  {+   "name": "create_symbol",+   "tier": "core",+   "summary": "Create one rectangular symbol from a pin list in a SchLib (the library is created and saved if the path does not exist) and return a proof frame.",+   "input": {+    "bodyHeightMil": "int, default 600",+    "bodyWidthMil": "int, default 800",+    "description": "string",+    "designator": "string, default 'U'",+    "libraryPath": "path to a .SchLib (required)",+    "name": "string (required)",+    "pins": "'des,name,left|right|top|bottom,offset_mil[,part[,hiddenNet]];...' - part 1..N makes a multi-part symbol (a dual op-amp: its A pins part 1, B pins part 2), 0 puts a pin on every part (power); hiddenNet hides the pin and joins it to that net by name (shared power pins: '8,V+,left,300,0,VCC', or the compiler reports duplicate pins)",+    "symbols": "[{name, pins, ...}] - optional: make several in one call (each over the shared args)",+    "timeout": "seconds, default 90"+   },+   "output": {+    "data": "{symbol, library, libraryState: opened|created, pins: int}",+    "shot": "{image: base64 png, width, height}",+    "windowEtiquette": "background"+   },+   "hint": "Library is UNSAVED: altium_set_params {footprint, params}, then altium_save {filePath: libraryPath}. For multi-mode symbols use altium_build_symbol_modes.",+   "pitfalls": [+    "A fresh library carries the stray Component_1: altium_cleanup_libs.",+    "Pin offsets are mils along the chosen side; check shot.image for overlapping pins."+   ],+   "related": [+    "build_symbol_modes",+    "set_params",+    "read_symbol",+    "save"+   ],+   "example": {+    "libraryPath": "C:/lib/Test.SchLib",+    "name": "CH340G",+    "pins": "1,GND,left,100;2,TXD,left,200;16,VCC,right,100"+   },+   "longRunning": false,+   "timeoutSeconds": 90+  },+  {+   "name": "doc_params",+   "tier": "core",+   "summary": "Read or set a sheet's document parameters: the title block (Title, DocumentNumber, Revision, DrawnBy, Author, CompanyName, ...).",+   "input": {+    "save": "bool, default true",+    "set": "{name: value} (optional; missing parameters are added)",+    "sheet": "path to the .SchDoc (required)"+   },+   "output": {+    "params": "[{name, value}]",+    "saved": "bool",+    "set": "int"+   },+   "hint": "The title block shows them through =Title, =DocumentNumber, =Revision ... special strings.",+   "pitfalls": [],+   "related": [+    "sch_edit",+    "export_pdf"+   ],+   "example": {+    "set": {+     "Revision": "B",+     "Title": "12 V to 5 V buck"+    },+    "sheet": "C:/proj/Main.SchDoc"+   },+   "longRunning": false,+   "timeoutSeconds": 60+  },+  {+   "name": "find_symbol",+   "tier": "core",+   "summary": "Find symbols (or footprints with kind:'footprint') by part of their name across libraries: the given ones, or every installed .SchLib/.PcbLib.",+   "input": {+    "kind": "symbol (default) | footprint",+    "libraries": "[paths] - optional, default the installed libraries",+    "query": "part of the name (required)"+   },+   "output": {+    "best": "the exact match, else the shortest name containing it",+    "matches": "[{name, library, exact}]"+   },+   "hint": "altium_show_symbol / altium_read_symbol on the best match; it is a style reference for a new symbol too.",+   "pitfalls": [+    ".IntLib files are not searched (altium_extract_intlib gives their sources); the Altium 365 workspace is searched with altium_workspace_search."+   ],+   "related": [+    "show_symbol",+    "read_symbol",+    "list_library",+    "workspace_search"+   ],+   "example": {+    "query": "LM358"+   },+   "longRunning": false,+   "timeoutSeconds": 300+  },+  {+   "name": "footprint_primitives",+   "tier": "core",+   "summary": "Geometry dump of one PLACED component: its tracks, arcs, pads (with solder and paste mask expansion in um) and texts in mils with layer ids; other primitive kinds are counted.",+   "input": {+    "designator": "string (required) - a component on the current board, e.g. U1"+   },+   "output": {+    "data": "{designator, pattern, count, otherPrimitives, primitives: [{kind: track|arc|pad|text, layerId, ...}]}",+    "roundtripMs": "int"+   },+   "hint": "Use it to verify an imported footprint on a altium_test_board: pad layerId 74 = multi-layer (plated through), which is what altium_plate_vias produces.",+   "pitfalls": [+    "Needs a .PcbDoc current: it reads a placed component, not a PcbLib member (altium_pcb_report reads libraries).",+    "3D bodies, fills and regions only show up in otherPrimitives."+   ],+   "related": [+    "component_pins",+    "plate_vias",+    "test_board",+    "pcb_report"+   ],+   "example": {+    "designator": "U1"+   },+   "longRunning": false,+   "timeoutSeconds": 120+  },+  {+   "name": "layer_stack",+   "tier": "core",+   "summary": "The CURRENT board's stack-up: copper layers in order with copper thickness and the dielectric under each (height, constant, material), and the total thickness. For impedance, current capacity and fab quotes.",+   "input": {},+   "output": {+    "data": "{copperLayers, totalUm, layers: [{name, copperUm, dielectricUm (the dielectric BELOW this layer), dk1000 (dielectric constant x 1000), material}]}"+   },+   "hint": "Open the board first (altium_show_2d_board). dk1000 is the dielectric constant times 1000 (4200 = 4.2).",+   "pitfalls": [+    "Solder mask and overlay layers are not part of this list; the copper and the dielectric under each are."+   ],+   "related": [+    "layers",+    "copper_layers",+    "read_rules"+   ],+   "example": {},+   "longRunning": false,+   "timeoutSeconds": 60+  },+  {+   "name": "layers",+   "tier": "core",+   "summary": "The current board's layer set with ids, names, kind and whether each is displayed.",+   "input": {},+   "output": {+    "data": "{layers: [{name, id, kind, displayed}]}",+    "roundtripMs": "int"+   },+   "hint": "ids feed altium_set_layer_visibility {layerId}. For what is actually used, altium_layers_in_use.",+   "pitfalls": [+    "Needs a .PcbDoc current.",+    "The fixed layer set, not the stackup: thickness and dielectric detail are not read."+   ],+   "related": [+    "copper_layers",+    "layers_in_use",+    "set_layer_visibility"+   ],+   "example": {},+   "longRunning": false,+   "timeoutSeconds": 90+  },+  {+   "name": "layers_in_use",+   "tier": "core",+   "summary": "Which layers of the current board actually carry primitives: copper layers with copper (densest first) and every layer with anything on it.",+   "input": {+    "limit": "int, default 2000",+    "sections": "string csv - optional"+   },+   "output": {+    "board": "path",+    "copperLayersInUse": "[{layerId, name, primitives}]",+    "layersWithPrimitives": "[{layerId, name, primitives}]",+    "roundtripMs": "int"+   },+   "hint": "The honest answer to 'how many layers is this board'. altium_set_layer_visibility {layerId} to isolate one, then altium_show_2d_board for a frame.",+   "pitfalls": [+    "Needs a .PcbDoc current.",+    "A floor, not the stack: an empty inner plane carries no primitives and is invisible here."+   ],+   "related": [+    "board_summary",+    "layers",+    "copper_layers",+    "set_layer_visibility"+   ],+   "example": {},+   "longRunning": false,+   "timeoutSeconds": 120+  },+  {+   "name": "list_designators",+   "tier": "core",+   "summary": "Every component designator on the current board.",+   "input": {},+   "output": {+    "data": "{count, designators: [string]}",+    "roundtripMs": "int"+   },+   "hint": "Then altium_component_data {designator} or altium_component_pins {designator}.",+   "pitfalls": [+    "Needs a .PcbDoc current: 'no current PCB board' carries the _hint to open one."+   ],+   "related": [+    "component_data",+    "component_pins",+    "read_board"+   ],+   "example": {},+   "longRunning": false,+   "timeoutSeconds": 90+  },+  {+   "name": "list_library",+   "tier": "core",+   "summary": "Names of every component in a .SchLib or every footprint in a .PcbLib (kind taken from the extension). Opens the library; no screenshot.",+   "input": {+    "libraryPath": "path to a .SchLib/.PcbLib (alias: filePath) - default: the bundled SpiritLevel SchLib",+    "limit": "int, default 500"+   },+   "output": {+    "data": "{kind: sch|pcb, count, names: [string]}",+    "kind": "sch | pcb",+    "path": "string",+    "roundtripMs": "int"+   },+   "hint": "Use the exact names with altium_show_symbol / altium_read_symbol / altium_show_footprint / altium_pcb_report.",+   "pitfalls": [+    "It makes the library the current document.",+    "count is the true total; names[] stops at limit."+   ],+   "related": [+    "show_library",+    "read_symbol",+    "pcb_report"+   ],+   "example": {+    "libraryPath": "C:/lib/Adom_ToF.PcbLib"+   },+   "longRunning": false,+   "timeoutSeconds": 60+  },+  {+   "name": "list_nets",+   "tier": "core",+   "summary": "Every net on the current board with its pad count.",+   "input": {},+   "output": {+    "data": "{count, nets: [{name, pads}]}",+    "roundtripMs": "int"+   },+   "hint": "Then altium_net_connections {net} for the pads of one net.",+   "pitfalls": [+    "Needs a .PcbDoc current.",+    "Pad counts are tallied over pads: a net with no pads on it does not appear."+   ],+   "related": [+    "net_connections",+    "read_board"+   ],+   "example": {},+   "longRunning": false,+   "timeoutSeconds": 90+  },+  {+   "name": "move_parts",+   "tier": "core",+   "summary": "Move and rotate placed components; each part is read back to 0.001 mm and the exact rotation. One undo step.",+   "input": {+    "boardId": "string (optional) - refused with board_mismatch unless this is the current board",+    "expectPath": "string (optional)",+    "expectedRevision": "string (optional) - the revision you last read; refused with stale_board if the board changed",+    "parts": "[{ref, x, y, rotation}] - mm and degrees, Altium's frame; or {ref, dx, dy} to move by an offset; rotation kept when left out"+   },+   "output": {+    "mismatches": "[{ref, asked, got}] when a part did not land where asked (errorCode readback_mismatch)",+    "results": "[ok|err:...]",+    "revision": "string"+   },+   "hint": "Rotation is absolute, not a delta.",+   "pitfalls": [+    "Moving to the other side of the board is not supported yet."+   ],+   "related": [+    "routing_state",+    "route_net"+   ],+   "example": {+    "parts": [+     {+      "ref": "U1",+      "rotation": 90,+      "x": 112.0,+      "y": 92.0+     }+    ]+   },+   "longRunning": false,+   "timeoutSeconds": 300+  },+  {+   "name": "net_connections",+   "tier": "core",+   "summary": "Every pad on the board with its net and owner (Designator.Pad), plus a bounding span and the airline length (minimum spanning tree over its pads, airlineMm) per net. net filters to one net.",+   "input": {+    "limit": "int, default 4000 - caps pads",+    "net": "string - optional net name"+   },+   "output": {+    "data": "{count, pads: [{net, pad: 'U1.3', xMil, yMil}], nets: [{net, minXminYmaxXmaxYpads}]}",+    "roundtripMs": "int"+   },+   "hint": "Pass net on anything but a small board. The span string is 'minX,minY,maxX,maxY,pads' in mils: a quick measure of how far a net travels.",+   "pitfalls": [+    "Needs a .PcbDoc current.",+    "limit truncates silently: count is what was returned, not what exists."+   ],+   "related": [+    "list_nets",+    "component_pins"+   ],+   "example": {+    "net": "GND"+   },+   "longRunning": false,+   "timeoutSeconds": 120+  },+  {+   "name": "normalize_bodies",+   "tier": "core",+   "summary": "Move every 3D body of a PcbLib's footprints (or one footprint) onto its pad cluster. A body added through the canvas lands 50000 mil from the pads in the stored frame, which makes Zoom All frame 50 inches of nothing.",+   "input": {+    "footprintName": "string - optional filter",+    "libraryPath": "path to a .PcbLib - optional; opened first",+    "timeout": "seconds, default 120"+   },+   "output": {+    "data": "{moved: int, shifts: string}",+    "roundtripMs": "int"+   },+   "hint": "altium_add_body already runs it. Use it on libraries built elsewhere when altium_show_footprint shows an empty canvas; then altium_save.",+   "pitfalls": [+    "Without libraryPath it acts on whatever PcbLib is current ('no PCB library is current' otherwise).",+    "It centres bodies on the pad-cluster centre: a deliberately offset body is moved too."+   ],+   "related": [+    "add_body",+    "pcb_report",+    "show_3d_chip",+    "save"+   ],+   "example": {+    "libraryPath": "C:/lib/Adom_ToF.PcbLib"+   },+   "longRunning": false,+   "timeoutSeconds": 120+  },+  {+   "name": "part_parameters",+   "tier": "core",+   "summary": "The parameter names a project's parts carry (with how many parts carry each), or with name, that parameter's value on every part.",+   "input": {+    "name": "a parameter name - optional",+    "projectPath": "path to the .PrjPcb (required)"+   },+   "output": {+    "names": "[{name, parts}]",+    "values": "[{designator, value, sheet}] (with name)"+   },+   "hint": "Change values with altium_sheet_params; altium_bom groups the purchasing ones by MPN.",+   "pitfalls": [],+   "related": [+    "sheet_params",+    "bom",+    "schematic_data"+   ],+   "example": {+    "name": "MPN",+    "projectPath": "C:/proj/board.PrjPcb"+   },+   "longRunning": false,+   "timeoutSeconds": 300+  },+  {+   "name": "place_on_pcb",+   "tier": "core",+   "summary": "Place ONE footprint from a PcbLib on a new board and return a proof frame. The single-part predecessor of altium_test_board.",+   "input": {+    "footprintName": "string",+    "libraryPath": "path to a .PcbLib (required)",+    "pcbPath": "path to save the new .PcbDoc - optional",+    "timeout": "seconds, default 90"+   },+   "output": {+    "data": "{pcb, placed, libFile, libBodies, primitives, padsOnComp, bodiesOnComp}",+    "shot": "{image: base64 png, width, height}",+    "windowEtiquette": "background"+   },+   "hint": "Prefer altium_test_board (outline, several parts, nets). bodiesOnComp:0 with libBodies>0 means the body did not copy: report it.",+   "pitfalls": [+    "Every call creates a NEW PcbDoc; without pcbPath it exists only unsaved.",+    "'footprint not found in PcbLib' - names from altium_list_library."+   ],+   "related": [+    "test_board",+    "place_on_sch",+    "show_3d_board"+   ],+   "example": {+    "footprintName": "VL53L5CX",+    "libraryPath": "C:/lib/Adom_ToF.PcbLib",+    "pcbPath": "C:/proj/one.PcbDoc"+   },+   "longRunning": false,+   "timeoutSeconds": 90+  },+  {+   "name": "place_on_sch",+   "tier": "core",+   "summary": "Place one symbol, or a ';' list of symbols, from the open SchLib on ONE new sheet (sheet sized FROM the measured parts: 500 mil gaps, 600 mil margins, A4 minimum; parts inside the frame as U1..Un with the part number as Comment); fills the title block from title/number/revision/drawnBy and places description as a text frame; saves to schPath when given. When schPath names an EXISTING .SchDoc the symbols are APPENDED to it (right of the current ones, designators continuing; sheet size and title block untouched, issue 11).",+   "input": {+    "description": "string (what the sheet shows, 2-3 sentences)",+    "drawnBy": "string",+    "libraryPath": "path (.SchLib)",+    "number": "string (DocumentNumber)",+    "revision": "string",+    "schPath": "path (.SchDoc, optional save)",+    "symbolName": "string | 'A;B;C'",+    "title": "string (Title)"+   },+   "output": {+    "count": "int",+    "designator": "string",+    "placed": "string",+    "primitives": "int",+    "sch": "path"+   },+   "hint": "Three parts on one sheet: symbolName 'VL53L5CX;VL53L7CX;VL53L8CX' + schPath, then altium_show_schematic {filePath: schPath}.",+   "pitfalls": [+    "Every call creates a NEW SchDoc; there is no append-to-sheet yet.",+    "The SchLib must be the current document (the verb opens it)."+   ],+   "related": [+    "test_board",+    "show_schematic",+    "read_schematic"+   ],+   "example": {+    "libraryPath": "C:/lib/Adom_ToF.SchLib",+    "schPath": "C:/proj/tof.SchDoc",+    "symbolName": "VL53L5CX;VL53L7CX;VL53L8CX"+   },+   "longRunning": false,+   "timeoutSeconds": 90+  },+  {+   "name": "place_vias",+   "tier": "core",+   "summary": "Place vias (through, top to bottom). One undo step, read back.",+   "input": {+    "boardId": "string (optional) - refused with board_mismatch unless this is the current board",+    "expectPath": "string (optional)",+    "expectedRevision": "string (optional) - the revision you last read; refused with stale_board if the board changed",+    "vias": "[{net, x, y, size, drill}] - mm"+   },+   "output": {+    "counts": "{vias, ...}",+    "results": "[ok|err:...]",+    "revision": "string"+   },+   "hint": "Through vias, top to bottom, on the named net. Heatsink vias under a pad: place them here, then pour or route to them.",+   "pitfalls": [],+   "related": [+    "route_net",+    "routing_state"+   ],+   "example": {+    "vias": [+     {+      "drill": 0.3,+      "net": "GND",+      "size": 0.6,+      "x": 110.0,+      "y": 90.0+     }+    ]+   },+   "longRunning": false,+   "timeoutSeconds": 300+  },+  {+   "name": "plate_vias",+   "tier": "core",+   "summary": "Make every drilled pad in a PcbLib a plated MULTI-LAYER pad (all footprints, or footprintName). adom-lbr writes heatsink vias as pads with a hole; Altium loaded them on the Top Layer, so a thermal pad's vias were not through-holes until this ran.",+   "input": {+    "footprintName": "string (optional filter)",+    "libraryPath": "path (.PcbLib)"+   },+   "output": {+    "fixed": "Fp:Pad;...",+    "holes": "int",+    "plated": "int"+   },+   "hint": "altium_save the PcbLib afterwards; footprint_primitives on a placed part should then report layerId 74 for the vias.",+   "pitfalls": [+    "Read-modify in place: run once per library install."+   ],+   "related": [+    "normalize_bodies",+    "footprint_primitives",+    "save"+   ],+   "example": {+    "libraryPath": "C:/lib/Adom_ToF.PcbLib"+   },+   "longRunning": false,+   "timeoutSeconds": 90+  },+  {+   "name": "read_board",+   "tier": "core",+   "summary": "ONE round trip: components, nets, layers, copper, rules, occupancy, outline.",+   "input": {+    "limit": "int",+    "sections": "string csv - optional"+   },+   "output": {+    "data": "object - the agent's payload",+    "roundtripMs": "int",+    "success": "bool"+   },+   "hint": "The primitive every board view derives from; board_summary / rules_by_kind / layers_in_use are shaped from the same read.",+   "pitfalls": [+    "Needs a .PcbDoc current."+   ],+   "related": [+    "board_summary",+    "review_board"+   ],+   "example": {},+   "longRunning": false,+   "timeoutSeconds": 120+  },+  {+   "name": "read_rules",+   "tier": "core",+   "summary": "Every design rule on the current board, flat, disabled ones included: name, kind ordinal, enabled, priority, both scope expressions, comment, and Altium's descriptor with the rule's VALUES (e.g. 'Clearance Constraint (Gap=6mil) (All),(All)').",+   "input": {},+   "output": {+    "data": "{count, rules: [{name, kind: int, enabled, priority, scope1, scope2, comment, descriptor}]}",+    "roundtripMs": "int"+   },+   "hint": "altium_rules_by_kind groups the enabled ones under readable kind names; altium_run_drc checks the board against them.",+   "pitfalls": [+    "Needs a .PcbDoc current.",+    "kind is Altium's TRuleKind ORDINAL; the values are in descriptor, as Altium words them."+   ],+   "related": [+    "rules_by_kind",+    "run_drc"+   ],+   "example": {},+   "longRunning": false,+   "timeoutSeconds": 90+  },+  {+   "name": "read_schematic",+   "tier": "core",+   "summary": "Components on the CURRENT schematic sheet (designator, library reference, comment, position, parameters) in one agent round trip.",+   "input": {+    "limit": "int, default 500",+    "params": "bool, default true - include component parameters",+    "sections": "string csv, default 'components'"+   },+   "output": {+    "components": "[{designator, libReference, comment, ...}]",+    "count": "int",+    "designators": "[string]",+    "document": "string",+    "roundtripMs": "int"+   },+   "hint": "Then altium_cross_check {board, sheet} to compare against the layout, or altium_schematic_data {designator} for one part's pins.",+   "pitfalls": [+    "The CURRENT sheet only: a hierarchical parent holds sheet symbols, not parts. Read each leaf sheet.",+    "Needs a .SchDoc current; 'no current schematic' -> altium_open_document / altium_show_schematic."+   ],+   "related": [+    "schematic_data",+    "cross_check",+    "show_schematic",+    "activate_document"+   ],+   "example": {},+   "longRunning": false,+   "timeoutSeconds": 90+  },+  {+   "name": "read_symbol",+   "tier": "core",+   "summary": "Read a SchLib component back: description, display-mode count, the pins of mode 0 (designator, name, electrical type), every parameter with its hidden flag, and its model links (footprints, 3D, sim).",+   "input": {+    "libraryPath": "path to a .SchLib - optional; opened first",+    "symbolName": "string",+    "timeout": "seconds, default 90"+   },+   "output": {+    "data": "{libref, description, displayModeCount, pinsMode0: int, pins: [{des, name, electrical: 0 Input, 1 IO, 2 Output, 3 OpenCollector, 4 Passive, 5 HiZ, 6 OpenEmitter, 7 Power}], params: [{name, value, hidden}], models: [{type, name, isCurrent}]}",+    "roundtripMs": "int"+   },+   "hint": "The verification step after altium_import_part / altium_set_params: models[] shows which footprint the symbol is bound to (type PCBLIB, isCurrent true for the one a placed part uses). For the drawing use altium_symbol_prims.",+   "pitfalls": [+    "The SCH Library panel's Model pane can lag behind altium_show_symbol; models[] here is the symbol's own record.",+    "Only mode 0 pins are listed."+   ],+   "related": [+    "symbol_prims",+    "set_params",+    "list_library",+    "show_symbol"+   ],+   "example": {+    "libraryPath": "C:/lib/Adom_ToF.SchLib",+    "symbolName": "VL53L5CX"+   },+   "longRunning": false,+   "timeoutSeconds": 90+  },+  {+   "name": "remove_footprint",+   "tier": "core",+   "summary": "Delete one footprint from the current PcbLib.",+   "input": {+    "footprintName": "string",+    "libraryPath": "path to a .PcbLib - optional; opened first",+    "timeout": "seconds, default 90"+   },+   "output": {+    "data": "{removed: string}",+    "roundtripMs": "int"+   },+   "hint": "altium_save afterwards. Symbols that linked it keep a dangling model: altium_set_models.",+   "pitfalls": [+    "No undo through the bridge; closing the library without saving is the way back."+   ],+   "related": [+    "remove_symbol",+    "cleanup_libs",+    "list_library",+    "save"+   ],+   "example": {+    "footprintName": "VL53L5CX",+    "libraryPath": "C:/lib/Adom_ToF.PcbLib"+   },+   "longRunning": false,+   "timeoutSeconds": 90+  },+  {+   "name": "remove_items",+   "tier": "core",+   "summary": "Delete specific free tracks and vias, given exactly as altium_routing_state returned them. One undo step.",+   "input": {+    "boardId": "string (optional) - refused with board_mismatch unless this is the current board",+    "expectPath": "string (optional)",+    "expectedRevision": "string (optional) - the revision you last read; refused with stale_board if the board changed",+    "tracks": "[{start{x,y}, end{x,y}, layer}]",+    "vias": "[{position{x,y}}] or [{x,y}]"+   },+   "output": {+    "results": "[ok|err:not found]",+    "revision": "string"+   },+   "hint": "Undo a bad segment or via without touching the rest of the net; altium_ripup_nets clears a whole net.",+   "pitfalls": [+    "Matching is exact to the internal unit: pass the coordinates you read, not rounded ones."+   ],+   "related": [+    "ripup_nets",+    "routing_state"+   ],+   "example": {+    "vias": [+     {+      "x": 110.0,+      "y": 90.0+     }+    ]+   },+   "longRunning": false,+   "timeoutSeconds": 300+  },+  {+   "name": "remove_symbol",+   "tier": "core",+   "summary": "Delete one component from the current SchLib.",+   "input": {+    "libraryPath": "path to a .SchLib - optional; opened first",+    "symbolName": "string",+    "timeout": "seconds, default 60"+   },+   "output": {+    "data": "{removed: string}",+    "roundtripMs": "int"+   },+   "hint": "The repair step for a symbol with duplicate models: remove, altium_import_part again, altium_save.",+   "pitfalls": [+    "No undo through the bridge; the library is unsaved until altium_save, so altium_close_document without saving is the way back."+   ],+   "related": [+    "import_part",+    "cleanup_libs",+    "list_library",+    "save"+   ],+   "example": {+    "libraryPath": "C:/lib/Adom_ToF.SchLib",+    "symbolName": "VL53L5CX"+   },+   "longRunning": false,+   "timeoutSeconds": 60+  },+  {+   "name": "rename_footprint",+   "tier": "core",+   "summary": "Rename a footprint in the current PcbLib.",+   "input": {+    "from": "string",+    "libraryPath": "path to a .PcbLib - optional; opened first",+    "timeout": "seconds, default 90",+    "to": "string"+   },+   "output": {+    "data": "{renamed: string}",+    "roundtripMs": "int"+   },+   "hint": "altium_save, then re-link symbols that referenced the old name: altium_set_models {footprint: <new name>}.",+   "pitfalls": [+    "'to' is NOT checked: an empty or already-used name is applied as given.",+    "Symbols link footprints BY NAME: a rename silently breaks their models."+   ],+   "related": [+    "copy_footprint",+    "rename_symbol",+    "set_models",+    "save"+   ],+   "example": {+    "from": "PCBComponent_1",+    "libraryPath": "C:/lib/Adom_ToF.PcbLib",+    "to": "VL53L5CX"+   },+   "longRunning": false,+   "timeoutSeconds": 90+  },+  {+   "name": "rename_symbol",+   "tier": "core",+   "summary": "Rename a component (LibReference) in the current SchLib.",+   "input": {+    "from": "string",+    "libraryPath": "path to a .SchLib - optional; opened first",+    "timeout": "seconds, default 90",+    "to": "string"+   },+   "output": {+    "data": "{renamed: string}",+    "roundtripMs": "int"+   },+   "hint": "altium_save, then altium_list_library to confirm.",+   "pitfalls": [+    "'to' is NOT checked: an empty or already-used name is applied as given. altium_list_library first."+   ],+   "related": [+    "clone_symbol",+    "rename_footprint",+    "list_library",+    "save"+   ],+   "example": {+    "from": "Component_1",+    "libraryPath": "C:/lib/Adom_ToF.SchLib",+    "to": "VL53L5CX"+   },+   "longRunning": false,+   "timeoutSeconds": 90+  },+  {+   "name": "ripup_nets",+   "tier": "core",+   "summary": "Delete every free track, arc and via on each named net. One undo step.",+   "input": {+    "boardId": "string (optional) - refused with board_mismatch unless this is the current board",+    "expectPath": "string (optional)",+    "expectedRevision": "string (optional) - the revision you last read; refused with stale_board if the board changed",+    "nets": "[string]"+   },+   "output": {+    "results": "[ok:<deleted count>]",+    "revision": "string"+   },+   "hint": "Pours are not touched.",+   "pitfalls": [],+   "related": [+    "remove_items",+    "route_net"+   ],+   "example": {+    "nets": [+     "SW",+     "VOUT"+    ]+   },+   "longRunning": false,+   "timeoutSeconds": 300+  },+  {+   "name": "route_net",+   "tier": "core",+   "summary": "Route one net as tracks, segment by segment; a waypoint on another layer drops a via there. One undo step, read back.",+   "input": {+    "boardId": "string (optional) - refused with board_mismatch unless this is the current board",+    "expectPath": "string (optional)",+    "expectedRevision": "string (optional) - the revision you last read; refused with stale_board if the board changed",+    "layer": "F.Cu|B.Cu|In<n>.Cu or an Altium layer id (default F.Cu)",+    "net": "string (required)",+    "paths": "[[waypoint,...]] - a waypoint is [x,y], {x,y,layer} or {pad:\"U1.3\"}",+    "viaDrill": "mm (default 0.3)",+    "viaSize": "mm (default 0.6)",+    "width": "mm (default 0.25)"+   },+   "output": {+    "applied": "int",+    "counts": "{tracks, vias, ...}",+    "failed": "int",+    "results": "[ok|err:...] per segment/via",+    "revision": "string - the board after the edit",+    "state": "the full altium_routing_state after",+    "unconnected": "int"+   },+   "hint": "Coordinates are mm in Altium's frame, the same as altium_routing_state. There is no clearance check here yet: run your own DRC (AI Flow's gate) or altium_run_drc after.",+   "pitfalls": [+    "Needs the board current.",+    "A net name must already exist on the board (nets come from the schematic / pads)."+   ],+   "related": [+    "routing_state",+    "place_vias",+    "remove_items",+    "ripup_nets"+   ],+   "example": {+    "net": "VOUT",+    "paths": [+     [+      {+       "pad": "L1.2"+      },+      [+       120.0,+       90.0+      ],+      {+       "pad": "C6.1"+      }+     ]+    ],+    "width": 0.5+   },+   "longRunning": false,+   "timeoutSeconds": 300+  },+  {+   "name": "routing_state",+   "tier": "core",+   "summary": "The WHOLE current board as data, in mm: tracks, arcs, vias, pads (with owner and net), components (ref, x, y, rotation, side), polygon pours, the outline, the unrouted (ratsnest) count, and a revision hash.",+   "input": {+    "expectPath": "string (optional) - refuse with wrong_board unless this .PcbDoc is the current board"+   },+   "output": {+    "boardId": "string (the .PcbDoc path)",+    "components": "[{ref, x, y, rotation, side, pattern}]",+    "counts": "{tracks, arcs, vias, pads, components, polygons, nets}",+    "outline": "[[x,y]]",+    "pads": "[{ref, name, x, y, sizeX, sizeY, rotation, layer, drill, net}]",+    "polygons": "[{net, layer, points[[x,y]]}]",+    "revision": "string - pass as expectedRevision to the edit verbs",+    "tracks": "[{start{x,y}, end{x,y}, width, layer, net}]",+    "unconnected": "int",+    "vias": "[{position{x,y}, size, drill, layers[2], net}]"+   },+   "hint": "Layers use KiCad names (F.Cu, B.Cu, In<n>.Cu; *.Cu = a multi-layer pad) with Altium's layerId alongside. Coordinates are Altium's own frame (y up), in mm. This is the read AI Flow's Altium lane calls before and after every edit.",+   "pitfalls": [+    "Needs a .PcbDoc current (altium_show_2d_board {filePath}).",+    "Footprint-internal tracks and arcs are left out; only free copper is listed.",+    "unconnected counts Altium's ratsnest lines: re-read after an edit for a fresh count."+   ],+   "related": [+    "read_board",+    "net_connections",+    "footprint_primitives"+   ],+   "example": {+    "expectPath": "C:/designs/buck/buck.PcbDoc"+   },+   "longRunning": false,+   "timeoutSeconds": 180+  },+  {+   "name": "rules_by_kind",+   "tier": "core",+   "summary": "The current board's ENABLED design rules grouped by rule kind (Clearance, Width, RoutingVias, ...), each group sorted by priority. Shaped from one read_board round trip.",+   "input": {+    "limit": "int, default 2000",+    "sections": "string csv - optional"+   },+   "output": {+    "board": "path",+    "roundtripMs": "int",+    "rulesByKind": "{KindName: [{name, kind, enabled, priority, scope1, scope2, comment}]}"+   },+   "hint": "Use it to answer 'what clearance/width does this board enforce'. altium_read_rules gives the flat list including disabled rules; altium_run_drc checks the board against them.",+   "pitfalls": [+    "Needs a .PcbDoc current.",+    "Disabled rules are dropped; a rule kind the bridge has no name for appears as 'kind <n>'."+   ],+   "related": [+    "read_rules",+    "run_drc",+    "board_summary"+   ],+   "example": {},+   "longRunning": false,+   "timeoutSeconds": 120+  },+  {+   "name": "sch_connect",+   "tier": "core",+   "summary": "Connect pins of placed parts: a wire stub from each pin's outer end plus a net label (kind label), a power port (kind power: GND-like nets get a ground symbol) or the stub alone (kind wire); or mark a pin left open on purpose with a No ERC marker (kind noerc, no net). One undo step.",+   "input": {+    "connections": "[{designator, pin (number or name), net, kind: label|power|wire|noerc|port (default label), io?: input|output|bidirectional (port), stubMil (default 200)}] (required)",+    "save": "bool, default true",+    "sheet": "path to the .SchDoc (required)"+   },+   "output": {+    "connected": "int",+    "failed": "[..]",+    "results": "[{designator, pin, net, kind, result, endMil}]",+    "saved": "bool"+   },+   "hint": "Net labels with the same text connect across the project; power ports connect by net name everywhere. Check the result with altium_compile (ERC) or altium_schematic_data.",+   "pitfalls": [+    "A pin that already has a wire gets a second stub; read the sheet first when unsure.",+    "A part rotated after connecting leaves its stubs behind."+   ],+   "related": [+    "sch_place_part",+    "compile",+    "schematic_data"+   ],+   "example": {+    "connections": [+     {+      "designator": "U2",+      "kind": "power",+      "net": "GND",+      "pin": "1"+     },+     {+      "designator": "U2",+      "net": "VIN",+      "pin": "VIN"+     }+    ],+    "sheet": "C:/proj/Main.SchDoc"+   },+   "longRunning": false,+   "timeoutSeconds": 90+  },+  {+   "name": "sch_edit",+   "tier": "core",+   "summary": "Change placed parts on a sheet: move (to x,y mil), moveBy (dx,dy), rotate (degrees), rename (designator), re-comment (comment), delete. One undo step.",+   "input": {+    "edits": "[{designator, action: move|moveBy|rotate|designator|comment|delete, x,y | dx,dy | degrees | to | text}] (required)",+    "save": "bool, default true",+    "sheet": "path to the .SchDoc (required)"+   },+   "output": {+    "applied": "int",+    "failed": "[..]",+    "results": "[{designator, action, result}]",+    "saved": "bool"+   },+   "hint": "Wires do not follow a moved part; reconnect with altium_sch_connect. Renames and deletions reach the board with altium_update_pcb.",+   "pitfalls": [+    "Every part of a multi-part component with that designator is changed together."+   ],+   "related": [+    "sch_connect",+    "update_pcb",+    "sheet_params"+   ],+   "example": {+    "edits": [+     {+      "action": "comment",+      "designator": "R5",+      "text": "10k"+     },+     {+      "action": "delete",+      "designator": "C9"+     }+    ],+    "sheet": "C:/proj/Main.SchDoc"+   },+   "longRunning": false,+   "timeoutSeconds": 90+  },+  {+   "name": "sch_place_part",+   "tier": "core",+   "summary": "Place a component from a .SchLib onto an EXISTING sheet at a position, with its library parameters (MPN, Manufacturer, distributor numbers) and its footprint link.",+   "input": {+    "comment": "string, default the library's",+    "designator": "string, default the library's (e.g. R?)",+    "footprint": "a PcbLib footprint name to link instead of the library's own",+    "library": "path to the .SchLib (required)",+    "partId": "int, default 1 (a multi-part component's part)",+    "pcbLib": "path of the .PcbLib the footprint lives in (optional)",+    "rotation": "0|90|180|270",+    "save": "bool, default true",+    "sheet": "path to the .SchDoc (required)",+    "symbol": "the component's name in that library (required)",+    "x": "mil, default 1000 (snapped to 100)",+    "y": "mil, default 1000"+   },+   "output": {+    "atMil": "[x, y]",+    "designator": "string",+    "footprints": "[string]",+    "libref": "string",+    "pins": "int",+    "saved": "bool"+   },+   "hint": "Then wire it: altium_sch_connect {sheet, connections:[{designator, pin, net}]}; number a '?' designator with altium_annotate; push it to the board with altium_update_pcb.",+   "pitfalls": [+    "Places part 1 of a multi-part component unless partId says otherwise.",+    "Sheet coordinates are mils from the sheet's lower-left corner; altium_schematic_data shows where the other parts are."+   ],+   "related": [+    "sch_connect",+    "annotate",+    "update_pcb",+    "list_library"+   ],+   "example": {+    "designator": "U2",+    "library": "C:/proj/Lib.SchLib",+    "sheet": "C:/proj/Main.SchDoc",+    "symbol": "TPS54202DDCR",+    "x": 3000,+    "y": 4000+   },+   "longRunning": false,+   "timeoutSeconds": 120+  },+  {+   "name": "sch_sheet_symbol",+   "tier": "core",+   "summary": "Make a design hierarchical: a sheet symbol on the parent sheet for a child sheet, its entries joined to the parent's nets, and matching ports on the child. A child that does not exist yet is made from Altium's sheet template and added to the project.",+   "input": {+    "child": "the child .SchDoc (required; a name alone sits beside the parent)",+    "entries": "[{name, io: input|output|bidirectional|passive, side?: left|right (default inputs left), net?: a parent net to join (a stub and a net label), stubMil?: 300}]",+    "name": "the symbol's designator (default the child's name)",+    "ports": "bool, default true: put a port on the child for each entry it lacks",+    "portsAt": "{left, right, top} mil on the child (defaults 1000, 15000, 9500)",+    "projectPath": ".PrjPcb: lists the child when it is not listed yet",+    "save": "bool, default true",+    "sheet": "the parent .SchDoc (required)",+    "widthMil": "default 1400",+    "x": "mil, the symbol's top-left corner (default 2000)",+    "y": "mil (default 8000)"+   },+   "output": {+    "childCreated": "bool",+    "ports": "{placed:[{name, io, atMil, stubEndMil}], alreadyThere:[name]}",+    "steps": "[childCreated | addedToProject]",+    "symbol": "{name, file, atMil, sizeMil, entries:[{name, side, io, net, atMil}]}"+   },+   "hint": "Then connect the child's circuit to its ports (altium_sch_connect kind:'port', or a wire to stubEndMil) and altium_compile {projectPath}: entries and ports join by name.",+   "pitfalls": [+    "Altium never places a sheet entry by itself (its position stays 0,0 and it draws nowhere): the bridge puts each one on the symbol's edge, 100 mil apart.",+    "Ports already on the child (by name) are reported in alreadyThere, not doubled."+   ],+   "related": [+    "sch_connect",+    "compile",+    "schematic_data",+    "build_schematic"+   ],+   "example": {+    "child": "Power.SchDoc",+    "entries": [+     {+      "io": "input",+      "name": "VIN",+      "net": "VBUS"+     },+     {+      "io": "output",+      "name": "3V3",+      "net": "3V3"+     },+     {+      "io": "passive",+      "name": "GND",+      "net": "GND"+     }+    ],+    "projectPath": "C:\\work\\Board.PrjPcb",+    "sheet": "C:\\work\\Top.SchDoc"+   },+   "longRunning": false,+   "timeoutSeconds": 240+  },+  {+   "name": "sch_swap_part",+   "tier": "core",+   "summary": "Replace a placed part with another library part (a different value, package or symbol) at the same place, designator and rotation, keeping its connections: where a new pin ends elsewhere, a wire joins it to where the old pin ended.",+   "input": {+    "designator": "the part to replace (required)",+    "footprint": "optional footprint name",+    "keepComment": "bool, default false (the new part's own comment)",+    "library": "path to the .SchLib (required)",+    "pcbLib": "optional .PcbLib for that footprint",+    "save": "bool, default true",+    "sheet": "path to the .SchDoc (required)",+    "symbol": "the new part's name in it (required)"+   },+   "output": {+    "newPinsNotOnOld": "[pin]",+    "now": "{libReference, footprints}",+    "oldPinsNotOnNew": "[pin]",+    "pinsJoined": "[{pin, from, to}]"+   },+   "hint": "Then altium_update_pcb carries the new part's footprint and parameters to the board. A workspace part: altium_workspace_place it beside, connect, then altium_sch_edit delete the old one.",+   "pitfalls": [+    "Pins are matched by number.",+    "Multi-part components are refused."+   ],+   "related": [+    "sch_place_part",+    "sch_edit",+    "sch_connect",+    "update_pcb"+   ],+   "example": {+    "designator": "C10",+    "library": "C:/proj/Lib.SchLib",+    "sheet": "C:/proj/Main.SchDoc",+    "symbol": "CC0402-100NF"+   },+   "longRunning": false,+   "timeoutSeconds": 120+  },+  {+   "name": "schematic_data",+   "tier": "core",+   "summary": "Components on a schematic sheet (filePath, else the CURRENT sheet) with their pins (number, name, electrical type), position and orientation. designator filters to one part.",+   "input": {+    "designator": "string - optional filter",+    "filePath": "path to the .SchDoc - optional, made current first (alias sheet)",+    "limit": "int, default 500",+    "paramNamesOnly": "bool, default false - parameter names without values (a library with blob parameters reads fast)",+    "params": "bool, default false - include each component's parameters",+    "pins": "bool, default true"+   },+   "output": {+    "data": "{document, count, components: [{designator, partId, libReference, comment, description (empty for a workspace-managed part), kind (ComponentKind: 0 Standard, 1 Mechanical, 2 Graphical, 3 Net Tie BOM, 4 Net Tie, 5 Standard no BOM, 6 Jumper), itemGuid, vaultGuid, sourceLibrary, xMil, yMil, orientation, pins: [{number, name, electrical (0 Input, 1 IO, 2 Output, 3 OpenCollector, 4 Passive, 5 HiZ, 6 OpenEmitter, 7 Power), xMil, yMil, orientation, lengthMil}], params: []}]}",+    "roundtripMs": "int"+   },+   "hint": "Pin-level view of a sheet. params[] is empty unless you pass params:true; altium_read_schematic is the fuller parameter view.",+   "pitfalls": [+    "One sheet per call. Without filePath the current document must be a .SchDoc: a schematic library is refused (no_current_schematic), not read as an empty sheet.",+    "electrical is Altium's pin-type ordinal, not a name."+   ],+   "related": [+    "read_schematic",+    "cross_check",+    "show_schematic"+   ],+   "example": {+    "designator": "U1"+   },+   "longRunning": false,+   "timeoutSeconds": 120+  },+  {+   "name": "set_models",+   "tier": "core",+   "summary": "Replace every footprint model on a SchLib component with ONE PCBLIB model (set_params footprint and add_models APPEND, which is how a symbol ends up with two).",+   "input": {+    "footprint": "string (PcbLib footprint name)",+    "libraryPath": "path (.SchLib)",+    "symbolName": "string"+   },+   "output": {+    "footprint": "string",+    "removed": "int"+   },+   "hint": "altium_save the SchLib afterwards.",+   "pitfalls": [+    "Removes every existing model, including 3D-only ones."+   ],+   "related": [+    "symbol_prims",+    "set_params",+    "add_models"+   ],+   "example": {},+   "longRunning": false,+   "timeoutSeconds": 60+  },+  {+   "name": "set_outline",+   "tier": "core",+   "summary": "Reshape the current board's outline (a rectangle with rounded corners, or a polygon). One undo step.",+   "input": {+    "expectedRevision": "string (optional) - refused with stale_board if the board changed",+    "outline": "{rect:[x0,y0,w,h], cornerRadius?} or {points:[[x,y]...]} (mm)"+   },+   "output": {+    "outlineEdges": "[{kind: line|arc, ...}]",+    "revision": "string",+    "vertices": "int"+   },+   "hint": "Rounding corners: pass the same rectangle with cornerRadius (1 mm is a common default).",+   "pitfalls": [+    "Needs the board current."+   ],+   "related": [+    "build_board",+    "routing_state",+    "repour"+   ],+   "example": {+    "outline": {+     "cornerRadius": 1,+     "rect": [+      0,+      0,+      28,+      20+     ]+    }+   },+   "longRunning": false,+   "timeoutSeconds": 120+  },+  {+   "name": "set_params",+   "tier": "core",+   "summary": "Set parameters on a SchLib component, optionally its comment, and optionally link a footprint by name.",+   "input": {+    "comment": "string",+    "footprint": "string - PcbLib footprint name to link",+    "libraryPath": "path to a .SchLib (required)",+    "params": "{key: value} or 'Key=Value;Key=Value'",+    "symbolName": "string - omit for the FIRST component",+    "timeout": "seconds, default 90"+   },+   "output": {+    "data": "{component, paramsSet: int, footprint}",+    "roundtripMs": "int"+   },+   "hint": "Unsaved: altium_save {filePath: libraryPath}. 'LCSC Part #' and 'Manufacturer Part Number' are the JLC/BOM keys. altium_read_symbol shows the result.",+   "pitfalls": [+    "footprint APPENDS an implementation on every call; use altium_set_models to end up with exactly one.",+    "Parameters are created HIDDEN (visible ones pile up over the symbol): altium_set_visibility to show one.",+    "Without symbolName it edits the first component in the library."+   ],+   "related": [+    "read_symbol",+    "set_visibility",+    "set_models",+    "save"+   ],+   "example": {+    "comment": "VL53L5CX",+    "libraryPath": "C:/lib/Adom_ToF.SchLib",+    "params": {+     "LCSC Part #": "C2888092",+     "Manufacturer": "ST"+    },+    "symbolName": "VL53L5CX"+   },+   "longRunning": false,+   "timeoutSeconds": 90+  },+  {+   "name": "set_track_width",+   "tier": "core",+   "summary": "Set the width of the EXISTING tracks and arcs on nets or a net class (rules only steer new routing). Widen power nets after routing, or thin a net that fails clearance.",+   "input": {+    "filePath": "path to the .PcbDoc - optional",+    "layer": "top | bottom | mid N - optional, default every layer",+    "netClass": "class name (its nets)",+    "nets": "[string] board net names",+    "widthMm": "number (required)"+   },+   "output": {+    "byNet": "{NET: count}",+    "changed": "int",+    "drcAfter": "{errors, unconnected}"+   },+   "hint": "Run altium_routing_drc {full:true} after: wider copper can break clearance to neighbours.",+   "pitfalls": [+    "Copper inside footprints (pads' own tracks) is left alone."+   ],+   "related": [+    "net_classes",+    "route_net",+    "routing_drc",+    "add_rule"+   ],+   "example": {+    "netClass": "Power",+    "widthMm": 0.5+   },+   "longRunning": false,+   "timeoutSeconds": 120+  },+  {+   "name": "set_visibility",+   "tier": "core",+   "summary": "Show or hide one named parameter on a SchLib component.",+   "input": {+    "libraryPath": "path to a .SchLib - optional; opened first",+    "param": "string - the parameter name",+    "symbolName": "string",+    "timeout": "seconds, default 90",+    "visible": "bool, default true"+   },+   "output": {+    "data": "{param, visible: '1'|'0'}",+    "roundtripMs": "int"+   },+   "hint": "altium_save afterwards; altium_show_symbol to see where the text landed.",+   "pitfalls": [+    "'parameter not found' = the name must match a parameter altium_read_symbol lists.",+    "Visible parameters stack over the symbol body: show few."+   ],+   "related": [+    "set_params",+    "read_symbol",+    "show_symbol"+   ],+   "example": {+    "libraryPath": "C:/lib/Adom_ToF.SchLib",+    "param": "Manufacturer Part Number",+    "symbolName": "VL53L5CX",+    "visible": true+   },+   "longRunning": false,+   "timeoutSeconds": 90+  },+  {+   "name": "sheet_params",+   "tier": "core",+   "summary": "Write parameters onto PLACED parts on the schematic sheets (the BOM write-back): MPN, Manufacturer, Mouser/DigiKey/LCSC numbers, anything. A missing parameter is added hidden; one undo step per sheet; each touched sheet is saved.",+   "input": {+    "designators": "[string] or 'R1,R2' (required; every part of a multi-part component)",+    "params": "{name: value} or 'Key=Value;Key=Value' (required)",+    "projectPath": "path to the .PrjPcb: each designator is found on whichever sheet holds it",+    "save": "bool, default true",+    "sheet": "path to a .SchDoc - or",+    "timeout": "seconds, default 90"+   },+   "output": {+    "notFound": "[designator]",+    "sheets": "[{sheet, designators, paramsSet}]",+    "updated": "[designator]"+   },+   "hint": "The board picks the new parameters up with Design > Update PCB Document; altium_bom reads the sheets, so it shows them at once.",+   "pitfalls": [+    "Sheet parameters, not the library: altium_set_params changes a library part."+   ],+   "related": [+    "bom",+    "read_schematic",+    "set_params"+   ],+   "example": {+    "designators": [+     "R1",+     "R2"+    ],+    "params": {+     "MPN": "RC0402FR-0710KL",+     "Manufacturer": "YAGEO",+     "Mouser Part #": "603-RC0402FR-0710KL"+    },+    "projectPath": "C:/proj/board.PrjPcb"+   },+   "longRunning": false,+   "timeoutSeconds": 300+  },+  {+   "name": "silk_state",+   "tier": "core",+   "summary": "The silkscreen as it stands: every part's body box, its designator and comment (text, location, size, rotation, shown, Altium's measured box), and the free overlay texts and lines on both faces.",+   "input": {},+   "output": {+    "components": "[{ref, pattern, x, y, rotation, side, body:[l,b,r,t], name:{text,x,y,size,rotation,shown,box}, comment:{...}}]",+    "lines": "[{side, x1, y1, x2, y2, width, raw}]",+    "texts": "[{side, text, x, y, size, rotation, mirror, box, raw}]"+   },+   "hint": "The input to a silkscreen plan (AI Flow's `altium silkscreen`), and its check after landing: compare each box with the pads and vias from routing_state.",+   "pitfalls": [+    "Needs the board current.",+    "raw is the exact handle apply_silkscreen's remove takes."+   ],+   "related": [+    "apply_silkscreen",+    "routing_state"+   ],+   "example": {},+   "longRunning": false,+   "timeoutSeconds": 180+  },+  {+   "name": "swap_footprint",+   "tier": "core",+   "summary": "Replace the FIRST component on the current board with a footprint from a PcbLib at the same position, re-assigning nets by pad designator. Tracks are untouched, so the wiring survives.",+   "input": {+    "assigns": "'padDes=NET;...'",+    "footprintName": "string",+    "libraryPath": "path to the .PcbLib holding the new footprint",+    "pcbPath": "path to the .PcbDoc - optional; opened first",+    "timeout": "seconds, default 240"+   },+   "output": {+    "data": "{placed, padsOnComp, bodiesOnComp}",+    "windowEtiquette": "background"+   },+   "hint": "Built for single-part test boards (altium_test_board): iterate on a footprint and re-prove with altium_show_3d_board. altium_save the board afterwards.",+   "pitfalls": [+    "There is NO designator argument: on a multi-part board it replaces whichever component the iterator returns first.",+    "Nets come ONLY from assigns (padDes=NET, all pads sharing a designator get the net): pads you do not list end up with no net."+   ],+   "related": [+    "test_board",+    "show_3d_board",+    "footprint_primitives"+   ],+   "example": {+    "assigns": "1=GND;2=VDD",+    "footprintName": "VL53L5CX",+    "libraryPath": "C:/lib/Adom_ToF.PcbLib",+    "pcbPath": "C:/proj/tof.PcbDoc"+   },+   "longRunning": false,+   "timeoutSeconds": 240+  },+  {+   "name": "symbol_prims",+   "tier": "core",+   "summary": "Read a SchLib component's drawing: primitive counts by kind, body rectangle, polyline bounding box, display mode and the footprint models. Read-only, use it to verify add_symbol_art / set_models.",+   "input": {+    "libraryPath": "path (.SchLib)",+    "models": "bool, default false (the model walk halts the engine on some builds)",+    "symbolName": "string"+   },+   "output": {+    "bodyRectMil": "[l, b, r, t] - the union of the current display mode's rectangles (mode 0, as a sheet draws it)",+    "lines": "int",+    "models": "[\"PCBLIB:NAME\"]",+    "pins": "int",+    "polyBboxMil": "[..]",+    "polylines": "int",+    "rectangles": "int"+   },+   "hint": "polylines should equal what add_symbol_art reported and polyBboxMil should sit inside bodyRectMil.",+   "pitfalls": [],+   "related": [+    "add_symbol_art",+    "set_models",+    "read_symbol"+   ],+   "example": {},+   "longRunning": false,+   "timeoutSeconds": 60+  },+  {+   "name": "xcopy_footprint",+   "tier": "core",+   "summary": "Copy a footprint ACROSS libraries: from fromLib into toLib (opened and made current), optionally under a new name.",+   "input": {+    "from": "string - footprint name in the source",+    "fromLib": "path to the source .PcbLib",+    "timeout": "seconds, default 120",+    "to": "string - default: same as from",+    "toLib": "path to the destination .PcbLib - optional; else the current PcbLib"+   },+   "output": {+    "data": "{to, primitives: int}",+    "roundtripMs": "int"+   },+   "hint": "altium_pcb_report {path: toLib, name} to confirm pads and bodies arrived, then altium_save {filePath: toLib}.",+   "pitfalls": [+    "'open the destination PcbLib first' = pass toLib.",+    "'target already exists' in the destination: pass a different to."+   ],+   "related": [+    "copy_footprint",+    "pcb_report",+    "save"+   ],+   "example": {+    "from": "LGA-16",+    "fromLib": "C:/lib/Vendor.PcbLib",+    "to": "VL53L5CX",+    "toLib": "C:/lib/Adom_ToF.PcbLib"+   },+   "longRunning": false,+   "timeoutSeconds": 120+  },+  {+   "name": "add_rule",+   "tier": "native",+   "summary": "Add one SCOPED design rule that set_design_rules' board-wide values cannot express: a net's or class's clearance or width, a length limit, a matched-length group, a differential pair's width and gap, a routing via style, or how a net's pads join a pour (direct, relief, none).",+   "input": {+    "comment": "optional",+    "drill": "mm (via)",+    "gap": "mm (clearance; diffpair: the pair's gap)",+    "kind": "clearance|width|length|matched|diffpair|via|polyconnect",+    "max": "mm (width, optional; length: required)",+    "maxDrill": "via, optional",+    "maxGap": "diffpair, optional",+    "maxSize": "via, optional",+    "maxUncoupled": "mm, diffpair, optional",+    "maxWidth": "diffpair, optional",+    "min": "mm (width; length)",+    "minDrill": "via, optional",+    "minGap": "diffpair, optional",+    "minSize": "via, optional",+    "minWidth": "diffpair, optional",+    "name": "string - the rule is named 'Adom <name>'; the same name replaces it",+    "preferred": "mm (width, optional)",+    "reliefEntries": "2 | 4, polyconnect relief",+    "reliefWidth": "mm, polyconnect relief spokes",+    "remove": "bool - delete the Adom rule of this kind and name instead",+    "scope1": "Altium query, e.g. InNet('SW'), InNetClass('DDR_D0'), InDifferentialPair('USB')",+    "scope2": "query (default All; clearance, and polyconnect: the polygon, e.g. InPolygon('GND pour'))",+    "size": "mm (via)",+    "style": "polyconnect: direct | relief | none",+    "tolerance": "mm (matched)",+    "width": "mm (diffpair)"+   },+   "output": {+    "name": "string",+    "priority": "int (1 = first of its kind)"+   },+   "hint": "AI Flow's net clearances (a switch node kept farther from everything) land here. A scope with a typo matches nothing and DRC then passes silently: check it in the rules editor (Test Queries).",+   "pitfalls": [+    "Needs the board current.",+    "One rule per call."+   ],+   "related": [+    "set_design_rules",+    "read_rules",+    "routing_drc"+   ],+   "example": {+    "gap": 0.3,+    "kind": "clearance",+    "name": "SW node",+    "scope1": "InNet('SW')",+    "scope2": "All"+   },+   "longRunning": false,+   "timeoutSeconds": 60+  },+  {+   "name": "annotate",+   "tier": "native",+   "summary": "Number every '?' designator (R?, C?, U?) across the project's sheets: the lowest free number per prefix, top-left first on each sheet. dryRun shows the plan.",+   "input": {+    "dryRun": "bool, default false",+    "projectPath": "path to the .PrjPcb (required)",+    "save": "bool, default true"+   },+   "output": {+    "planned": "[..] (dryRun)",+    "renamed": "[{sheet, from, to, result}]",+    "skippedMultiPart": "[{sheet, designator, libReference, partId}]"+   },+   "hint": "Then altium_update_pcb carries the designators to the board.",+   "pitfalls": [+    "Multi-part components with '?' are left alone (their parts share one designator); use Altium's Tools > Annotation for those."+   ],+   "related": [+    "sch_place_part",+    "update_pcb",+    "sch_edit"+   ],+   "example": {+    "projectPath": "C:/proj/board.PrjPcb"+   },+   "longRunning": false,+   "timeoutSeconds": 300+  },+  {+   "name": "apply_pours",+   "tier": "native",+   "summary": "Add solid polygon pours from outlines and pour them, each with its own connect style (solid joints or thermal spokes), plus pour keepouts (polygon cutouts); answers each pour's real poured copper area. One undo step.",+   "input": {+    "cutouts": "(optional) [{layer, points:[[x,y]...] mm, name?}] - areas every pour on that layer stays out of (KiCad's pour keepout); landed before the pours",+    "expectedRevision": "string (optional) - refused with stale_board if the board changed since you read it",+    "pours": "[{net, layer (F.Cu|B.Cu|In<n>.Cu), points:[[x,y]...] mm, order?: pour order (1 = poured first), name?: polygon name, connect?: direct|relief|none, reliefWidth?: mm spoke width, reliefGap?: mm air gap (not settable by script on AD 26: Altium keeps 10 mil and notes[] says so), reliefEntries?: 2|4, removeDead?: bool (default true), removeSmallIslands?: bool (default false: connected copper of any size stays, as in KiCad; true = Altium's own 2500 sq mil island filter), minNeck?: mm - copper narrower than this is removed (KiCad's zone min thickness), solidPads?: [[x1,y1,x2,y2]] mm - pads completely inside each rect join THIS pour solid whatever its connect (KiCad's small solid joint zone over a pad; Altium pours nothing for a same-net polygon inside another)}]"+   },+   "output": {+    "filledAreaByLayerMm2": "{F.Cu: mm2, ...}",+    "results": "[ok:<polygon name>|err:...]",+    "revision": "string",+    "unfilled": "[zone] that poured nothing",+    "zones": "[{net, layer, outline, filledMm2, islands, filled}]"+   },+   "hint": "connect writes a PolygonConnect rule 'AdomPC <name>' scoped IsNamedPolygon('<name>') that outranks the board's own, so a power patch can join its pads solid while the ground pour keeps spokes. Without connect the board's Polygon Connect Style applies (Altium's default is 4 relief spokes of 10 mil, a current neck). Clearance comes from the board's Clearance rule. Dead copper is removed unless removeDead is false.",+   "pitfalls": [+    "Needs the board current.",+    "The outline need not repeat its first point.",+    "A name already on the board gets a ' 2' suffix: read the real name from results."+   ],+   "related": [+    "pour_state",+    "repour",+    "remove_pours",+    "routing_state",+    "set_design_rules"+   ],+   "example": {+    "cutouts": [+     {+      "layer": "F.Cu",+      "name": "Kelvin tap",+      "points": [+       [+        20,+        2+       ],+       [+        22,+        2+       ],+       [+        22,+        4+       ],+       [+        20,+        4+       ]+      ]+     }+    ],+    "pours": [+     {+      "connect": "relief",+      "layer": "B.Cu",+      "net": "GND",+      "points": [+       [+        0,+        0+       ],+       [+        28,+        0+       ],+       [+        28,+        20+       ],+       [+        0,+        20+       ]+      ],+      "reliefGap": 0.25,+      "reliefWidth": 0.35+     },+     {+      "connect": "direct",+      "layer": "F.Cu",+      "net": "SW",+      "order": 1,+      "points": [+       [+        10,+        5+       ],+       [+        16,+        5+       ],+       [+        16,+        9+       ],+       [+        10,+        9+       ]+      ]+     }+    ]+   },+   "longRunning": false,+   "timeoutSeconds": 600+  },+  {+   "name": "autoroute",+   "tier": "native",+   "summary": "Altium's own autorouter (Situs) on the current board: picks a strategy, keeps existing routing locked by default, routes, and answers the unconnected count and DRC errors before and after. Not saved unless save:true.",+   "input": {+    "filePath": "path to the .PcbDoc - optional, else the current board",+    "lockPreroutes": "bool, default true - existing tracks are kept",+    "ripupViolations": "bool, default false",+    "save": "bool, default false",+    "strategy": "Default 2 Layer Board | Default Multi Layer Board | Default 2 Layer With Edge Connectors | General Orthogonal | Via Miser | Cleanup (default: the dialog's current)",+    "timeout": "seconds, default 600"+   },+   "output": {+    "after": "{unconnected, errors}",+    "before": "{unconnected, errors}",+    "saved": "bool",+    "steps": "[..]"+   },+   "hint": "Look at the result before keeping it (altium_show_2d_board) and check it with altium_routing_drc. Altium's rules drive Situs: set them first (altium_set_design_rules, altium_add_rule).",+   "pitfalls": [+    "Parts outside the board outline get routed to where they sit: place them first (altium_move_parts).",+    "With lockPreroutes:false Situs rips up and reroutes existing tracks."+   ],+   "related": [+    "route_net",+    "routing_drc",+    "set_design_rules",+    "save"+   ],+   "example": {+    "filePath": "C:/proj/board.PcbDoc",+    "strategy": "Default 2 Layer Board"+   },+   "longRunning": false,+   "timeoutSeconds": 900+  },+  {+   "name": "build_symbol_modes",+   "tier": "native",+   "summary": "Build ONE SchLib component with N display modes (Normal + Alternate 1..N-1) from a symbol_modes spec file on this machine, and return a proof frame.",+   "input": {+    "libraryPath": "path to a .SchLib (required; created if missing)",+    "spec": "Windows path to the spec (tools/symbol_modes.py output; mils, Y up) (required)",+    "timeout": "seconds, default 240"+   },+   "output": {+    "data": "{libref, library, libraryState, modes, pins, lines, displayModeCount}",+    "shot": "{image: base64 png, width, height}",+    "windowEtiquette": "background"+   },+   "hint": "Then altium_add_symbol_art for the chip outline, altium_add_models / altium_set_models for the footprint, altium_save. altium_symbol_mode {mode} frames each alternate.",+   "pitfalls": [+    "libref comes from the spec's META line.",+    "Compare data.displayModeCount with data.modes before trusting the alternates.",+    "Library is left UNSAVED."+   ],+   "related": [+    "import_part",+    "add_symbol_art",+    "symbol_mode",+    "add_models"+   ],+   "example": {+    "libraryPath": "C:/lib/Adom_ToF.SchLib",+    "spec": "C:/lib/specs/VL53L5CX.sym.txt"+   },+   "longRunning": false,+   "timeoutSeconds": 240+  },+  {+   "name": "cleanup_libs",+   "tier": "native",+   "summary": "Remove the factory-default stray every fresh library carries: Component_1 from the current SchLib and an empty (no pads) PCBComponent_1 from the current PcbLib.",+   "input": {+    "libraryPath": "path to a .SchLib/.PcbLib - optional; opened first",+    "timeout": "seconds, default 90"+   },+   "output": {+    "data": "{removed: int}",+    "roundtripMs": "int"+   },+   "hint": "Run once per new library after the first import, then altium_save and altium_list_library.",+   "pitfalls": [+    "The SchLib side removes ANY component named Component_1 without checking that it is empty.",+    "removed:0 is normal on a library that was already clean."+   ],+   "related": [+    "list_library",+    "remove_symbol",+    "remove_footprint",+    "save"+   ],+   "example": {+    "libraryPath": "C:/lib/Adom_ToF.SchLib"+   },+   "longRunning": false,+   "timeoutSeconds": 90+  },+  {+   "name": "compile",+   "tier": "native",+   "summary": "Compile a project and return Altium's Messages panel (ERC + connectivity) with counts by class. With no path it compiles Altium's bundled example project.",+   "input": {+    "limit": "int, default 200 messages",+    "objectKind": "string, default 'Project'",+    "projectPath": "path to a .PrjPcb (alias: filePath) - default: the bundled Bluetooth Sentinel example",+    "timeout": "seconds"+   },+   "output": {+    "byClass": "{class: n}",+    "elapsedSec": "float",+    "messageCount": "int",+    "messages": "[{class, source, document, text}]",+    "project": "path",+    "windowEtiquette": "background"+   },+   "hint": "Zero messages = clean. Fix what messages[] names, recompile, then altium_run_drc for the board side.",+   "pitfalls": [+    "ALWAYS pass projectPath: the default compiles Altium's example project in the user's session.",+    "messages[] is the WHOLE Messages panel after the compile, capped at limit; messageCount is the true total."+   ],+   "related": [+    "run_erc",+    "run_drc",+    "open_project",+    "cross_check"+   ],+   "example": {+    "projectPath": "C:/proj/board.PrjPcb"+   },+   "longRunning": false,+   "timeoutSeconds": 180+  },+  {+   "name": "component_classes",+   "tier": "native",+   "summary": "List, make or remove component classes on the board, each optionally with a room: a placement area that keeps its parts together (fitted around the parts or a rectangle). The classes scope rules and group a channel for replicate_layout.",+   "input": {+    "filePath": "the .PcbDoc (opened first)",+    "members": "[designators]",+    "name": "class name (omit to list)",+    "remove": "bool - delete the class and its room",+    "room": "true (fit around the members' pads, 1 mm margin) | {marginMm} | {rect: [x1, y1, x2, y2] mm}"+   },+   "output": {+    "classes": "[{name, members}] (list)",+    "members": "[designator]",+    "missing": "[designator not on the board]",+    "room": "Altium's description of the room rule",+    "roomMm": "[x1, y1, x2, y2]"+   },+   "hint": "Use a class per channel or block: rules scope with InComponentClass('<name>'), and the room shows the block's area to the user and to DRC.",+   "pitfalls": [+    "The room is a confinement rule ('Adom room <name>', top layer): a part outside it is a DRC violation, not a move.",+    "The same name replaces the class and its room."+   ],+   "related": [+    "net_classes",+    "replicate_layout",+    "add_rule",+    "find_space"+   ],+   "example": {+    "members": [+     "U1",+     "R1",+     "C1"+    ],+    "name": "CH1",+    "room": true+   },+   "longRunning": false,+   "timeoutSeconds": 120+  },+  {+   "name": "diff_pairs",+   "tier": "native",+   "summary": "Differential pairs on the current board: list them, define one from two nets (replacing a pair of that name), or remove one. Altium's differential-pair router and rules work on these.",+   "input": {+    "filePath": "path to the .PcbDoc - optional",+    "name": "pair name; omit to list",+    "neg": "negative net (e.g. USB_N)",+    "pos": "positive net (e.g. USB_P)",+    "remove": "bool"+   },+   "output": {+    "pair": "string",+    "pairs": "[{name, pos, neg}] (list)",+    "replaced": "bool"+   },+   "hint": "Pair impedance and gap come from Altium's Differential Pairs Routing rule; set width/clearance for the pair's nets with altium_net_classes or altium_add_rule.",+   "pitfalls": [+    "Both nets must exist on the board."+   ],+   "related": [+    "net_classes",+    "list_nets",+    "add_rule"+   ],+   "example": {+    "name": "USB",+    "neg": "USB_DN",+    "pos": "USB_DP"+   },+   "longRunning": false,+   "timeoutSeconds": 90+  },+  {+   "name": "export_bom",+   "tier": "native",+   "summary": "Bill of materials export: altium_run_outjob with the assembly OutJob and the 'Folder Structure' medium as defaults.",+   "input": {+    "medium": "string, default 'Folder Structure'",+    "outjobPath": "path to an .OutJob - default: the bundled example Assembly.OutJob",+    "projectPath": "path to the .PrjPcb",+    "timeout": "seconds, default 300"+   },+   "output": {+    "dialogs": "[..]",+    "elapsedSec": "float",+    "files": "[relative paths]",+    "outjob": "path",+    "outputDir": "path | null",+    "surface": "bom"+   },+   "hint": "files[] names the BOM the OutJob wrote; pull it with ab's pull_file. For part data without an OutJob, altium_read_schematic returns parameters per component.",+   "pitfalls": [+    "Without outjobPath it exports the BOM of Altium's EXAMPLE design.",+    "The default OutJob is the example's Assembly.OutJob, which runs every output in that medium, not only a BOM."+   ],+   "related": [+    "run_outjob",+    "export_gerber",+    "read_schematic"+   ],+   "example": {+    "outjobPath": "C:/proj/Assembly.OutJob",+    "projectPath": "C:/proj/board.PrjPcb"+   },+   "longRunning": false,+   "timeoutSeconds": 300+  },+  {+   "name": "export_gerber",+   "tier": "native",+   "summary": "Gerber/drill export: altium_run_outjob with the fabrication OutJob and the 'Folder Structure' medium as defaults.",+   "input": {+    "medium": "string, default 'Folder Structure'",+    "outjobPath": "path to an .OutJob - default: the bundled example Fabrication.OutJob",+    "projectPath": "path to the .PrjPcb",+    "timeout": "seconds, default 300"+   },+   "output": {+    "dialogs": "[..]",+    "elapsedSec": "float",+    "files": "[relative paths]",+    "outjob": "path",+    "outputDir": "path | null",+    "surface": "gerber"+   },+   "hint": "Check files[] for the gerber and NC drill files, then pull them with ab's pull_file. Close the CAMtastic documents the OutJob opened (altium_state, altium_close_document).",+   "pitfalls": [+    "There is NO board-path argument: without outjobPath it exports Altium's EXAMPLE design. The user's project needs its own OutJob.",+    "OutJobs open what they generate: expect CAM documents left open in the user's session."+   ],+   "related": [+    "run_outjob",+    "export_bom",+    "export_step",+    "run_drc"+   ],+   "example": {+    "outjobPath": "C:/proj/Fabrication.OutJob",+    "projectPath": "C:/proj/board.PrjPcb"+   },+   "longRunning": false,+   "timeoutSeconds": 300+  },+  {+   "name": "export_manufacturing",+   "tier": "native",+   "summary": "A project's manufacturing set in one call: Gerbers, NC drill, pick and place (CSV, mm) and the BOM, from an OutJob the bridge writes beside the project (composed from this Altium's own OutJob templates, every output on one medium that opens nothing).",+   "input": {+    "bomColumns": "(optional) [column or parameter name] - default Comment, Description, Designator, Footprint, Quantity, MPN, Manufacturer, Mouser/DigiKey/LCSC Part #",+    "clean": "bool (default true) - empty the output folder first",+    "formats": "[odb, ipc2581] - more fab formats beside the Gerbers (ODB++ folder + .tgz, IPC-2581 .cvg); default Gerbers only",+    "pcbPath": "its .PcbDoc",+    "projectPath": "the .PrjPcb",+    "silkPrep": "bool (default true) - Altium's Silkscreen Preparation first (silk off the pads, text moved where it fits)",+    "silkPrepCheck": "bool (default false) - DRC silk counts before/after in silkPrep",+    "timeout": "seconds, default 300"+   },+   "output": {+    "dialogs": "[..]",+    "elapsedSec": "float",+    "files": "[relative paths]",+    "outjob": "path",+    "outputDir": "path"+   },+   "hint": "Pull files[] with pull_file and check them against the board (layers, drill hits, every placed part in the pick and place and the BOM) before handing them over. AI Flow's `altium export` does both.",+   "pitfalls": [+    "The board must be a document of the project.",+    "The BOM is the compiled project's (schematic parts); board-only pads are not in it."+   ],+   "related": [+    "run_outjob",+    "build_schematic",+    "routing_state"+   ],+   "example": {+    "pcbPath": "C:\\work\\Buck.PcbDoc",+    "projectPath": "C:\\work\\Buck.PrjPcb"+   },+   "longRunning": false,+   "timeoutSeconds": 600+  },+  {+   "name": "export_pdf",+   "tier": "native",+   "summary": "Every sheet of a project as one PDF, plus on request the board's composite print, Altium's assembly drawings (top and bottom, designators on the parts) and its fabrication print, from an OutJob in a throwaway project; the user's project is not touched.",+   "input": {+    "assembly": "bool, default false - Altium's Assembly Drawings output (needs a board in the project)",+    "board": "bool, default false - the composite print",+    "fabrication": "bool, default false - Altium's PCB Print fabrication drawing",+    "output": "path of the .pdf, default <project>.pdf beside it",+    "projectPath": "path to the .PrjPcb (required)",+    "timeout": "seconds, default 300"+   },+   "output": {+    "assembly": "bool",+    "board": "bool",+    "fabrication": "bool",+    "outputBytes": "int",+    "pdfPath": "path",+    "sheets": "int"+   },+   "hint": "Pull the file with ab's pull_file.",+   "pitfalls": [],+   "related": [+    "run_outjob",+    "export_manufacturing"+   ],+   "example": {+    "projectPath": "C:/proj/board.PrjPcb"+   },+   "longRunning": false,+   "timeoutSeconds": 600+  },+  {+   "name": "export_step",+   "tier": "native",+   "summary": "Headless STEP export of a board. Default lane 'outjob': Altium's own ExportSTEP output run from a throwaway project in %TEMP% that points at the board - no dialog, no click (measured on AD 26.9.1). lane:'extension' is the older AdomBridge .NET extension path.",+   "input": {+    "componentsOption": "int, Altium ExportComponentOptions, default 0 (all)",+    "copperOption": "int, Altium ExportCopperOption, default 0 (no copper)",+    "filePath": "path to a .PcbDoc (saved state is exported) - default: the bundled example board",+    "folded": "bool, default false",+    "holes": "bool, default true (mechanical, electrical and free pad holes)",+    "lane": "outjob | extension, default outjob",+    "modelsOption": "int, Altium ExportModelsOption, default 2 (template default)",+    "nameOccurrences": "bool, default true - name each component occurrence after its designator, as KiCad's STEP does (step2glb --molecule finds MP1-MP4 by it)",+    "output": "path - default: <board>.step",+    "singlePart": "bool, default false (one merged part)",+    "suffix": "string, required with suffixType custom",+    "suffixType": "none | board | custom | id, default none - Altium always names parts by DESIGNATOR; this only picks the suffix",+    "timeout": "seconds, default 300"+   },+   "output": {+    "elapsedSec": "float",+    "lane": "string",+    "occurrences": "{named: int} - component occurrences named after their designator",+    "output": "path",+    "outputBytes": "int",+    "outputExists": "bool",+    "products": "[string] - STEP PRODUCT names (how parts are named)"+   },+   "hint": "outputExists and products[] are the proof; then pull the file with ab's pull_file. Save the board first: the export reads the file on disk.",+   "pitfalls": [+    "Part names are designators (J1, U3...), never the Comment or footprint: Altium has no option for that (suffixType 0-3 measured).",+    "Altium leaves every assembly occurrence unnamed; the bridge names each board component's occurrence after its designator (0.2.54), so tools that find parts by occurrence name (step2glb --molecule's MP1-MP4 anchoring) work as on a KiCad STEP.",+    "The throwaway project and OutJob are closed afterwards and the temp folder removed; the user's own project is never opened or changed.",+    "pcb_not_found / step_not_written / bad_args are named; dialogs from the OutJob come back under outjob.dialogs."+   ],+   "related": [+    "run_outjob",+    "save",+    "show_3d_board"+   ],+   "example": {+    "filePath": "C:/proj/board.PcbDoc",+    "output": "C:/proj/board.step"+   },+   "longRunning": false,+   "timeoutSeconds": 360+  },+  {+   "name": "install_library",+   "tier": "native",+   "summary": "Register a library file in Altium's Installed Libraries (IntegratedLibraryManager.InstallLibrary) so its parts appear in the Components panel.",+   "input": {+    "libraryPath": "path to a .IntLib/.SchLib/.PcbLib/.DbLib (required)",+    "timeout": "seconds, default 90"+   },+   "output": {+    "data": "{installed: path}",+    "roundtripMs": "int"+   },+   "hint": "Takes effect live, no restart. For a wiki bundle zip use altium_install_library_bundle (unpack + register + proof).",+   "pitfalls": [+    "'library not found' = the path must exist on THIS machine.",+    "Registers the file where it is: moving or deleting it later leaves a dead entry."+   ],+   "related": [+    "install_library_bundle",+    "list_library",+    "show_library"+   ],+   "example": {+    "libraryPath": "C:/lib/Adom_ToF.SchLib"+   },+   "longRunning": false,+   "timeoutSeconds": 150+  },+  {+   "name": "install_library_bundle",+   "tier": "native",+   "summary": "One-click install of a wiki library bundle (zip of .SchLib/.PcbLib): unpack, register, open the library, prove it with a frame.",+   "input": {+    "capture": "bool",+    "captureMaxWidth": "int",+    "installId": "string - echoed in the receipt so a client can match it",+    "libraryName": "string",+    "zipPath": "string - staged zip"+   },+   "output": {+    "atMs": "int",+    "components": "[names]",+    "installId": "string",+    "phasesMs": "{launch, licence, unpack, register, open, list, panel, capture, agentStop}",+    "shot": "{image, ...}",+    "shown": "bool",+    "symbolCount": "int",+    "thumbnailDataUrl": "string",+    "totalMs": "int"+   },+   "hint": "Launches a closed Altium first (up to 240s), then registers and opens the library. Past ab's budget the verb keeps running: poll altium_status until lastInstall.installId matches, then altium_install_receipt for the full receipt.",+   "pitfalls": [+    "The receipt lives in bridge memory; a bridge restart clears it."+   ],+   "related": [+    "install_receipt",+    "status",+    "install_library"+   ],+   "example": {+    "capture": true,+    "installId": "Lib-1757350000000",+    "libraryName": "Lib",+    "zipPath": "C:\\Users\\me\\adom-wiki-libs\\Lib-altium.zip"+   },+   "longRunning": true,+   "timeoutSeconds": 330+  },+  {+   "name": "list_installed_libraries",+   "tier": "native",+   "summary": "Every library in Altium's Installed Libraries list (read-only).",+   "input": {},+   "output": {+    "count": "int",+    "libraries": "[path]"+   },+   "hint": "Check before and after altium_install_library / altium_uninstall_library without a screenshot.",+   "pitfalls": [+    "Needs the agent (it reads IntegratedLibraryManager inside Altium)."+   ],+   "related": [+    "uninstall_library",+    "install_library",+    "install_library_bundle"+   ],+   "example": {},+   "longRunning": false,+   "timeoutSeconds": 30+  },+  {+   "name": "net_classes",+   "tier": "native",+   "summary": "Net classes on the current board: list them with their nets, or make one (replacing a class of that name) with an optional width and clearance rule scoped to it, or remove one with its rules. Power nets wider, a high-voltage class with more clearance.",+   "input": {+    "clearance": "mm - optional rule 'Adom <name> clearance' (class to All)",+    "filePath": "path to the .PcbDoc - optional, else the current board",+    "name": "class name; omit to list",+    "nets": "[string] board net names (required to make one)",+    "remove": "bool - delete the class and its two rules",+    "width": "mm (0.5) or {min, preferred, max} in mm - optional rule 'Adom <name> width'"+   },+   "output": {+    "classes": "[{name, superClass, nets}] (list)",+    "members": "int",+    "notOnBoard": "[string] nets that were not found",+    "rules": "[{kind, success, ..}]",+    "scope": "InNetClass('<name>')"+   },+   "hint": "Rules land first among their kind, so they outrank the board-wide ones. Existing tracks keep their width: re-route the class's nets or check with altium_run_drc. altium_save keeps it.",+   "pitfalls": [+    "Net names must exist on the board (altium_list_nets); missing ones are reported in notOnBoard, not created.",+    "A class made on the board, not in the schematic, can be removed by a later change order if the project's comparator removes board-only classes."+   ],+   "related": [+    "add_rule",+    "list_nets",+    "read_rules",+    "diff_pairs",+    "set_design_rules"+   ],+   "example": {+    "clearance": 0.3,+    "name": "Power",+    "nets": [+     "VIN",+     "VOUT",+     "GND",+     "SW"+    ],+    "width": 0.5+   },+   "longRunning": false,+   "timeoutSeconds": 90+  },+  {+   "name": "netlist",+   "tier": "native",+   "summary": "The compiled netlist of a project: every net with its pins (Designator.Pin) across every sheet, the hierarchy resolved; or one net; or compare with the nets you expect.",+   "input": {+    "expect": "optional {net: [\"U1.3\", ...]}: compare pin by pin",+    "net": "optional: one net",+    "projectPath": ".PrjPcb (required)"+   },+   "output": {+    "compileErrors": "[messages]",+    "mismatches": "[{net, missing, extra} | {net, unexpected}] (expect)",+    "netCount": "int",+    "nets": "{net: [pin]}"+   },+   "hint": "Use it to check a schematic the AI drew or edited before altium_update_pcb: expect the nets you meant and read mismatches.",+   "pitfalls": [+    "Unnamed nets read as Altium names them (NetU2_3).",+    "Compiles the project first: the reply is the design as Altium sees it now."+   ],+   "related": [+    "compile",+    "schematic_data",+    "sch_connect",+    "sch_sheet_symbol",+    "update_pcb"+   ],+   "example": {+    "expect": {+     "GND": [+      "U1.4",+      "U10.4"+     ],+     "VBUS": [+      "U1.1",+      "U10.1"+     ]+    },+    "projectPath": "C:\\\\work\\\\Board.PrjPcb"+   },+   "longRunning": false,+   "timeoutSeconds": 300+  },+  {+   "name": "panelize",+   "tier": "native",+   "summary": "Step and repeat a board into a manufacturing panel: a new .PcbDoc holding Altium's own embedded board array of the board (rows x cols), with rails, the panel frame on the outline layer, three tooling holes and three fiducials; boards separated by JLCPCB-style V-score lines or mouse-bite tabs, with JLCPCB's rules checked. The array stays linked to the board.",+   "input": {+    "cols": "default 2",+    "gapMm": "between boards, default 2 (vscore: 0)",+    "overwrite": "bool - replace an existing panelPath (closed in Altium unsaved first); without it an existing file is refused",+    "panelPath": "the new panel .PcbDoc",+    "pcbPath": "the board's .PcbDoc",+    "projectPath": "a .PrjPcb the panel joins (so altium_export_manufacturing can make its Gerbers)",+    "railMm": "rail width, default 5 (0 for none)",+    "rows": "default 1",+    "separation": "none (default) | vscore (boards edge to edge, V-CUT lines across the panel on Mechanical 1 with 'V-CUT' written at both ends, as JLCPCB asks) | tabs (mouse bites: 0.6 mm holes five to a tab, the rout path drawn on Mechanical 1)",+    "toolingHoles": "default true: 2 mm unplated holes and 1 mm fiducials when rails are 5 mm or wider"+   },+   "output": {+    "boardMm": "[w, h] of one board's outline",+    "boardsAt": "[[x, y]] each board's outline corner from the panel's corner",+    "jlcpcb": "{rules:[{rule, ok (true|false|null = a note), detail}], fits}",+    "nearestCopperToEdge": "{mm, at}",+    "panelMm": "[w, h]",+    "panelPath": "path",+    "project": "{path, added}",+    "saved": "bool",+    "separation": "{kind, cutsX, cutsY | tabs, holes, routLines, applied, failed}",+    "warnings": "[{copperOutsideOutline}]"+   },+   "hint": "Fix what jlcpcb.rules marks false (panel under 70 mm: more rows or wider rails; copper under 0.4 mm from a cut; rounded corners on a V-cut) and run it again with overwrite:true. Then altium_export_manufacturing {projectPath, pcbPath: the panel}.",+   "pitfalls": [+    "Opens the board, then creates and saves the panel; without projectPath the saved panel is closed (open it with altium_open_document).",+    "tabs refuses a board that draws its own outline on a mechanical layer: every copy brings it along closed and a router would cut each board free.",+    "JLCPCB's rules as published (V-cut standards and mouse-bite guide, read 2026-10-23): V-cut panels 70 to 475 mm, 0.6 mm boards or thicker, copper 0.4 mm (1.0 recommended) from a cut, at most 25 cuts a side; mouse bites 0.6 mm holes 5 to a set, gaps of 1.2 mm or more."+   ],+   "related": [+    "fab_check",+    "export_manufacturing",+    "board_features",+    "set_outline"+   ],+   "example": {+    "cols": 3,+    "panelPath": "C:/proj/Panel.PcbDoc",+    "pcbPath": "C:/proj/Board.PcbDoc",+    "projectPath": "C:/proj/Board.PrjPcb",+    "railMm": 5,+    "rows": 3,+    "separation": "vscore"+   },+   "longRunning": false,+   "timeoutSeconds": 240+  },+  {+   "name": "pcb_report",+   "tier": "native",+   "summary": "Numbers instead of pixels: for a PcbLib, every footprint's pads, pad bounding box, primitive census and 3D bodies; for a board, outline, components with nets and bodies, census and tracks.",+   "input": {+    "name": "string - footprint name filter (library form)",+    "path": "path to a .PcbLib or .PcbDoc - optional; opened first",+    "timeout": "seconds, default 60",+    "want": "'board' - report the focused BOARD even though a PcbLib is also answering"+   },+   "output": {+    "data": "{kind: library, components: [{name, pads, padsBboxMil, census, bodies}], bodyCols} | {kind: board, outlineMil, components: [{.., pads, padsBboxMil, nets, bodies}], census, tracks, bodyCols}",+    "roundtripMs": "int"+   },+   "hint": "bodies[] rows follow bodyCols (layerId,wMil,hMil,leftMil,bottomMil,standoffMil,overallMil,hasModel). A body whose leftMil is ~50000 away from padsBboxMil needs altium_normalize_bodies.",+   "pitfalls": [+    "Altium's GetCurrentPCBLibrary answers even when a BOARD is focused: pass want:'board' for a .PcbDoc or you get the library report.",+    "A footprint with bodies:[] places with no 3D model."+   ],+   "related": [+    "footprint_primitives",+    "normalize_bodies",+    "add_body",+    "show_3d_chip"+   ],+   "example": {+    "name": "VL53L5CX",+    "path": "C:/lib/Adom_ToF.PcbLib"+   },+   "longRunning": false,+   "timeoutSeconds": 60+  },+  {+   "name": "pour_state",+   "tier": "native",+   "summary": "Every polygon pour with the copper it actually poured: filled area per pour and per layer, and the pours that poured nothing.",+   "input": {},+   "output": {+    "filledAreaByLayerMm2": "{layer: mm2}",+    "unfilled": "[zone]",+    "zones": "[{net, name, layer, pourIndex, outline, filledMm2, islands, filled}]"+   },+   "hint": "filledMm2 is regions minus holes, computed from Altium's own poured geometry.",+   "pitfalls": [+    "Needs the board current.",+    "A shelved or unpoured polygon reads filledMm2 0."+   ],+   "related": [+    "apply_pours",+    "repour"+   ],+   "example": {},+   "longRunning": false,+   "timeoutSeconds": 180+  },+  {+   "name": "remove_pours",+   "tier": "native",+   "summary": "Delete polygon pours by net (and layer), or all of them, and the pour keepouts (cutouts) apply_pours made. Tracks and vias are untouched.",+   "input": {+    "cutouts": "(optional) \"all\" or [{layer}] - only the cutouts apply_pours landed (named Adom...)",+    "expectedRevision": "string (optional) - refused with stale_board if the board changed since you read it",+    "pours": "(optional) \"all\" or [{net, layer?} | {name} - one pour by the name apply_pours answered]"+   },+   "output": {+    "cutoutsRemoved": "int",+    "removed": "int",+    "revision": "string",+    "rulesRemoved": "int - AdomPC connect rules whose pour is gone"+   },+   "hint": "Use before re-pouring a changed plan; the connect rules of removed pours go with them. altium_ripup_nets clears copper.",+   "pitfalls": [],+   "related": [+    "apply_pours",+    "ripup_nets"+   ],+   "example": {+    "pours": [+     {+      "net": "GND"+     }+    ]+   },+   "longRunning": false,+   "timeoutSeconds": 300+  },+  {+   "name": "replicate_layout",+   "tier": "native",+   "summary": "Copy one channel's placement to matching channels: each target keeps its anchor part where it is and every other part takes the source's offset and rotation from the anchor (turned by the anchors' rotation difference).",+   "input": {+    "anchor": "optional ref from source (default the first)",+    "filePath": "optional .PcbDoc",+    "source": "[refs] of the placed channel (2 or more)",+    "targets": "[[refs], ...] - each in the same order as source"+   },+   "output": {+    "footprintMismatches": "[..] parts whose footprint differs from their source part",+    "moved": "int",+    "moves": "[{ref, x, y, rotation}]"+   },+   "hint": "Read the channels with altium_selection or altium_component_data; route the targets after (routing is not copied). Check with altium_find_space {check:true}.",+   "pitfalls": [+    "Routing and pours are not copied."+   ],+   "related": [+    "move_parts",+    "selection",+    "find_space"+   ],+   "example": {+    "source": [+     "U1",+     "R1",+     "C1"+    ],+    "targets": [+     [+      "U2",+      "R2",+      "C2"+     ],+     [+      "U3",+      "R3",+      "C3"+     ]+    ]+   },+   "longRunning": false,+   "timeoutSeconds": 180+  },+  {+   "name": "repour",+   "tier": "native",+   "summary": "Repour every polygon on the board (Altium's Repour All) and answer the copper after. Runs outside the script engine so scoped rules apply.",+   "input": {+    "inScript": "bool (default false) - repour from inside the agent instead (scoped rules then match nothing)"+   },+   "output": {+    "filledAreaByLayerMm2": "{...}",+    "run": "{helperExited, seconds, agentStart}",+    "unfilled": "[...]",+    "zones": "[...]"+   },+   "hint": "Run after moving parts or copper that pours flow around. Altium evaluates rule queries through its scripting system, so a repour from inside the resident agent ignores every scoped rule (a pour's own connect style, net-class clearances); this verb stops the agent, repours, and starts it again (a few seconds). apply_pours does the same on its own when it wrote connect rules.",+   "pitfalls": [+    "Needs the board current.",+    "The agent restarts: a verb sent meanwhile waits for it."+   ],+   "related": [+    "pour_state",+    "apply_pours"+   ],+   "example": {},+   "longRunning": false,+   "timeoutSeconds": 600+  },+  {+   "name": "routing_drc",+   "tier": "native",+   "summary": "Design-rule violations on the current board WITH locations, plus the unrouted (ratsnest) count, as one clean/not-clean answer.",+   "input": {+    "full": "bool (default false) - run Altium's batch DRC on this board first"+   },+   "output": {+    "clean": "bool - no violations and nothing unrouted",+    "errors": "int",+    "unconnected": "int",+    "violations": "[{rule, description, at{x,y} mm}]"+   },+   "hint": "Without full, this reads online DRC's live violations (fast). full:true runs the batch check through Altium's DRC dialog first.",+   "pitfalls": [+    "Needs the board current.",+    "violations[] is capped at 500; errors is the real count."+   ],+   "related": [+    "routing_state",+    "run_drc"+   ],+   "example": {+    "full": true+   },+   "longRunning": false,+   "timeoutSeconds": 300+  },+  {+   "name": "run_drc",+   "tier": "native",+   "summary": "Run the design rule check on a board and read the violation markers back. PCB:DesignRuleCheck opens a MODAL; the bridge clicks 'Run Design Rule Check' through UIA in the background, waits for the agent, re-activates the board and reads the violations.",+   "input": {+    "filePath": "path to a .PcbDoc - default: the bundled Bluetooth Sentinel board",+    "limit": "int, default 200 violations",+    "timeout": "seconds to wait for the DRC, default 180"+   },+   "output": {+    "board": "path",+    "dialogs": "[..]",+    "elapsedSec": "float",+    "steps": "[..]",+    "violationCount": "int",+    "violations": "[{rule, description}]",+    "windowEtiquette": "background"+   },+   "hint": "violationCount is the true total; violations[] stops at limit. Altium also opens its HTML DRC report as a new document: altium_state shows it, altium_close_document {path} removes it.",+   "pitfalls": [+    "ALWAYS pass filePath: the default runs DRC on Altium's example board.",+    "drc_dialog_not_found = the DRC dialog never opened; check that a PCB is focused (altium_window_info).",+    "Never run PCB:DesignRuleCheck through altium_run_command: the modal freezes the agent with nobody to click it."+   ],+   "related": [+    "read_rules",+    "rules_by_kind",+    "review_board",+    "compile"+   ],+   "example": {+    "filePath": "C:/proj/board.PcbDoc"+   },+   "longRunning": false,+   "timeoutSeconds": 300+  },+  {+   "name": "run_erc",+   "tier": "native",+   "summary": "Electrical rules check. Same handler as altium_compile: in Altium, compiling the project IS the ERC.",+   "input": {+    "limit": "int, default 200 messages",+    "objectKind": "string, default 'Project'",+    "projectPath": "path to a .PrjPcb (alias: filePath) - default: the bundled Bluetooth Sentinel example",+    "timeout": "seconds"+   },+   "output": {+    "byClass": "{class: n}",+    "elapsedSec": "float",+    "messageCount": "int",+    "messages": "[{class, source, document, text}]",+    "project": "path",+    "windowEtiquette": "background"+   },+   "hint": "byClass gives the error/warning split; read messages[].text and .document for each. Then altium_run_drc for the layout.",+   "pitfalls": [+    "ALWAYS pass projectPath: the default compiles Altium's example project.",+    "Needs the PROJECT path, not a sheet: a free .SchDoc has no project to compile."+   ],+   "related": [+    "compile",+    "run_drc",+    "review_board"+   ],+   "example": {+    "projectPath": "C:/proj/board.PrjPcb"+   },+   "longRunning": false,+   "timeoutSeconds": 180+  },+  {+   "name": "set_design_rules",+   "tier": "native",+   "summary": "Write a fab's design rules into the current board: clearance, track width, routing via style, hole size, annular ring, hole-to-hole and board-edge clearance (mm).",+   "input": {+    "annularRing": "mm",+    "clearance": "mm",+    "costlier": "with fab: allow JLCPCB's surcharged small vias (0.15/0.25 mm) instead of 0.3/0.5",+    "edgeClearance": "mm - copper to the board edge (the rule it creates is scoped OnCopper; silkscreen keeps its own rules)",+    "fab": "jlcpcb - fill every value left out from JLCPCB's published limits for this board's layers and copper weight",+    "hole": "{min, max?} mm",+    "holeToHole": "mm",+    "polygonConnect": "the board's catch-all polygon connect style: direct|relief|none, or {style, reliefWidth: mm, reliefEntries: 2|4} (per-pour rules from apply_pours keep theirs)",+    "trackWidth": "{min, preferred?, max?} mm",+    "via": "{diameter:{min, preferred?, max?}, drill:{min, preferred?, max?}} mm"+   },+   "output": {+    "failed": "[{kind, error}]",+    "landed": "[{kind, rulesWritten}]",+    "rules": "the board's rules after"+   },+   "hint": "AI Flow's `rules use` calls this with the fab pack's numbers (adom/pcb-design-rules). Each kind is written separately, so one this Altium build lacks fails alone.",+   "pitfalls": [+    "Needs the board current.",+    "Every existing rule of a kind gets the values; rule scopes are kept, except an Adom edge rule an earlier bridge scoped to All, which becomes OnCopper (#26)."+   ],+   "related": [+    "read_rules",+    "rules_by_kind",+    "build_board"+   ],+   "example": {+    "clearance": 0.15,+    "edgeClearance": 0.3,+    "hole": {+     "min": 0.2+    },+    "trackWidth": {+     "min": 0.15+    },+    "via": {+     "diameter": {+      "min": 0.45+     },+     "drill": {+      "min": 0.2+     }+    }+   },+   "longRunning": false,+   "timeoutSeconds": 300+  },+  {+   "name": "silkscreen_prep",+   "tier": "native",+   "summary": "Clean the silkscreen off the copper with Altium's own Tools > Silkscreen Preparation: silk clipped clear of every mask opening (pad) by the board's silk rule, designators moved off pads where they fit, silk outside the board deleted; the silkscreen DRC hits counted before and after.",+   "input": {+    "check": "bool, default true: DRC before and after",+    "clipLocked": "bool, default false",+    "deleteOutsideBoard": "bool, default true",+    "filePath": "the .PcbDoc (opened first)",+    "minRemainingMil": "shortest silk segment kept (Altium's default 5.9 mil)",+    "moveText": "bool, default true: move text off pads (within Altium's Max Distance) instead of clipping it",+    "save": "bool, default true",+    "selectedOnly": "bool, default false"+   },+   "output": {+    "after": "{same}",+    "before": "{silkToSolderMask, silkToSilk, silkToBoardRegion, maskSliver}",+    "dialogsLeft": "[anything Altium asked]",+    "silkOffCopper": "bool",+    "steps": "[toggles, OK]"+   },+   "hint": "Run it before fab outputs and before 3D or screenshot renders: fabs trim silk off pads anyway, but a board that shows ink on copper looks wrong.",+   "pitfalls": [+    "It uses the board's SilkToSolderMaskClearance rule (Use Design Rules stays on).",+    "Text that cannot move within Max Distance stays and is counted in after.silkToSolderMask."+   ],+   "related": [+    "silk_state",+    "apply_silkscreen",+    "run_drc",+    "export_manufacturing"+   ],+   "example": {+    "filePath": "C:\\\\work\\\\Board.PcbDoc"+   },+   "longRunning": false,+   "timeoutSeconds": 600+  },+  {+   "name": "symbol_mode",+   "tier": "native",+   "summary": "Show one display mode (0 = Normal, 1.. = alternates) of the CURRENT SchLib component in the editor and capture a frame. Drives the editor's own mode toggle, because setting DisplayMode alone does not repaint.",+   "input": {+    "libraryPath": "path to a .SchLib - optional; opened first",+    "mode": "int, default 0",+    "timeout": "seconds, default 60"+   },+   "output": {+    "data": "{mode, displayModeCount, libref}",+    "shot": "{image: base64 png, width, height}",+    "windowEtiquette": "background"+   },+   "hint": "Select the component first with altium_show_symbol {libraryPath, symbolName}, then one call per mode up to displayModeCount-1.",+   "pitfalls": [+    "There is no symbolName argument: it acts on the component current in the editor.",+    "Leave it on mode 0 when done: the editor keeps showing the last mode to the user."+   ],+   "related": [+    "show_symbol",+    "build_symbol_modes",+    "read_symbol"+   ],+   "example": {+    "libraryPath": "C:/lib/Adom_ToF.SchLib",+    "mode": 1+   },+   "longRunning": false,+   "timeoutSeconds": 60+  },+  {+   "name": "teardrops",+   "tier": "native",+   "summary": "Add (or remove) teardrops with Altium's own Tools > Teardrops: on vias and through-hole pads, SMD pads, track width changes and T-junctions, for the whole board or the selection; teardrops counted before and after.",+   "input": {+    "filePath": "the .PcbDoc (opened first)",+    "force": "bool, default false: add even where a teardrop breaks a rule",+    "remove": "bool, default false (add)",+    "save": "bool, default true",+    "selectedOnly": "bool, default false (all)",+    "smdPads": "bool, default true",+    "tJunctions": "bool, default true",+    "tracks": "bool, default true",+    "vias": "bool, default true"+   },+   "output": {+    "dialogsLeft": "[anything Altium asked]",+    "steps": "[mode, objects, toggles, OK]",+    "teardropsAfter": "int",+    "teardropsBefore": "int"+   },+   "hint": "Add teardrops after routing and before the final pour and DRC: altium_repour, then altium_run_drc.",+   "pitfalls": [+    "Altium's own sizes are used (via 70% width / 30% length, pads 200% / 100%, curved).",+    "force:true places teardrops that violate clearance: leave it off unless the user asks."+   ],+   "related": [+    "repour",+    "run_drc",+    "routing_state",+    "selection"+   ],+   "example": {+    "filePath": "C:\\\\work\\\\Board.PcbDoc"+   },+   "longRunning": false,+   "timeoutSeconds": 300+  },+  {+   "name": "uninstall_library",+   "tier": "native",+   "summary": "Remove libraries from Altium's Installed Libraries: the inverse of install_library / install_library_bundle. Library files are never touched.",+   "input": {+    "dryRun": "bool",+    "libraryName": "file stem: matches X.SchLib AND X.PcbLib",+    "libraryPath": "exact registered path"+   },+   "output": {+    "closedDocuments": "[path]",+    "notInstalled": "bool",+    "remaining": "int",+    "removed": "[path]"+   },+   "hint": "Any open document for a removed library is closed first. Close Altium with a plain altium_close afterwards: a close that discards changes can drop the unregistration.",+   "pitfalls": [+    "Nothing matching answers notInstalled:true (success), so a cleanup can run twice.",+    "discardChanges:true on altium_close can bring the removed library back on the next launch."+   ],+   "related": [+    "list_installed_libraries",+    "install_library",+    "close"+   ],+   "example": {+    "libraryName": "Adom_ToF"+   },+   "longRunning": false,+   "timeoutSeconds": 60+  },+  {+   "name": "update_pcb",+   "tier": "native",+   "summary": "Design > Update PCB Document from the schematic (the engineering change order): parts added, removed or renamed, nets and footprints updated on the board. Previews by default; execute:true runs it (Execute Changes, Close), refusing to delete board parts unless allowRemovals:true.",+   "input": {+    "allowRemovals": "bool, default false - let the change order delete board parts that are on no sheet",+    "execute": "bool, default false - false only previews",+    "projectPath": "path to the .PrjPcb (required)",+    "timeout": "seconds, default 300"+   },+   "output": {+    "afterExecute": "[string]",+    "board": "path",+    "changeOrder": "[string] - the dialog's first page (groups carry counts)",+    "dialogs": "[..] - anything Altium asked",+    "executed": "bool",+    "preview": "{addParts, removeParts, sheetParts, boardParts}",+    "steps": "[..]"+   },+   "hint": "Preview first (the default): removeParts are board parts on no sheet (test pads, fiducials, mounting holes) that the change order would DELETE; it refuses them unless allowRemovals:true. New parts land beside the board outline: place them (altium_move_parts) and check with altium_routing_drc.",+   "pitfalls": [+    "Save the sheets first (the sch_* verbs save by default).",+    "The project must contain a .PcbDoc."+   ],+   "related": [+    "annotate",+    "sch_place_part",+    "move_parts",+    "routing_drc"+   ],+   "example": {+    "projectPath": "C:/proj/board.PrjPcb"+   },+   "longRunning": false,+   "timeoutSeconds": 600+  },+  {+   "name": "assembly_check",+   "tier": "checks",+   "summary": "Ready for JLCPCB assembly? The board against both JLCPCB PCBA tiers as published (Economic and Standard): board or panel size, thickness and layers, parts on one or both sides, the smallest package and the finest pin pitch, fiducials, and the parts with no LCSC number; answers the cheapest tier every rule allows.",+   "input": {+    "filePath": "the .PcbDoc (opened first; else the current board)",+    "panelMm": "[w, h] when it ships in a panel (altium_panelize's panelMm)",+    "projectPath": "the .PrjPcb - checks every purchased BOM line has an LCSC number (altium_bom)"+   },+   "output": {+    "board": "{parts, bottomSideParts, finestPitch, smallestChip, tinyFootprints, fiducials, throughHoleParts}",+    "economic": "{ok, rules:[{rule, ok, detail}]}",+    "linesWithoutLcsc": "[..]",+    "service": "economic | standard | null",+    "standard": "{ok, rules}"+   },+   "hint": "Fix what the rules mark false: panelize a small board (altium_panelize, then panelMm), add fiducials (altium_board_features), find LCSC numbers (adom-parts-search). Then altium_bom (JLCPCB BOM) and altium_export_manufacturing (pick and place), and check every part's rotation in JLCPCB's preview.",+   "pitfalls": [+    "Pitch is the closest pad centres within one part of 3 or more SMD pads; package size is read from footprint names and two-pad parts' pad spacing.",+    "Rotations are not checked: JLCPCB's preview is the authority on orientation."+   ],+   "related": [+    "bom",+    "export_manufacturing",+    "panelize",+    "board_features",+    "fab_check"+   ],+   "example": {+    "filePath": "C:/proj/Board.PcbDoc",+    "projectPath": "C:/proj/Board.PrjPcb"+   },+   "longRunning": false,+   "timeoutSeconds": 300+  },+  {+   "name": "auto_classes",+   "tier": "checks",+   "summary": "Net classes from the nets' own names: GND, Power (VCC, VDD, 3V3, VBAT...), Clock, HighSpeed (USB, Ethernet, HDMI, PCIe, DDR, MIPI, LVDS, SATA...) and the differential pairs their _P/_N, +/-, DP/DN names spell; a proposal first, written as Altium net classes (Power with a width rule) and pairs with apply.",+   "input": {+    "apply": "bool - write the classes and pairs (default: propose only)",+    "filePath": "the .PcbDoc (opened first; else the current board)",+    "powerWidthMm": "the Power class's width rule, default 0.5"+   },+   "output": {+    "applied": "bool",+    "proposal": "{classes:{GND, Power, Clock, HighSpeed}, pairs:[{name, pos, neg}], unclassified}",+    "written": "[{class|pair, success}]"+   },+   "hint": "Then scope rules to them (InNetClass('Power')), size the pairs with altium_impedance {apply}, and check power with altium_current_capacity.",+   "pitfalls": [+    "Names only: a net called N1 stays unclassified. Read the proposal before apply."+   ],+   "related": [+    "net_classes",+    "diff_pairs",+    "impedance",+    "add_rule",+    "current_capacity"+   ],+   "example": {+    "apply": true,+    "powerWidthMm": 0.6+   },+   "longRunning": false,+   "timeoutSeconds": 180+  },+  {+   "name": "board_features",+   "tier": "checks",+   "summary": "Add the furniture every real board needs in one undo step: mounting holes (at points or the four corners), fiducials, keepout areas, and a stitching-via grid on a net that keeps clear of every other net's copper, every pad and the board edge.",+   "input": {+    "dryRun": "bool - plan only",+    "expectPath": "optional",+    "expectedRevision": "optional",+    "fiducials": "[{x, y, size?: 1, opening?: 2 (mask opening diameter), side?: top|bottom}]",+    "filePath": "the .PcbDoc (opened first; expectPath is opened too)",+    "keepouts": "[{rect: [x1, y1, x2, y2] mm, layer?: all (the Keep-Out layer, default) | F.Cu | B.Cu | In<n>.Cu}] - stitching in the same call keeps out of them",+    "mountingHoles": "[{x, y, drill?: 3.2, plated?: false, pad?: drill+2.8 when plated, net?}] or {corners: {inset: 4}, drill?, plated?, net?}",+    "stitching": "{net (required), pitch?: 5, size?: 0.6, drill?: 0.3, clearance?: 0.3, edge?: 1 (from the outline), region?: [[x,y],..], max?: 400, underParts?: false (vias stay out of each part's pad box)}"+   },+   "output": {+    "applied": "int",+    "planned": "{mountingHoles, fiducials, stitching:{count, points}}",+    "results": "[ok|err:..] per item",+    "state": "routing_state after"+   },+   "hint": "dryRun first to see the stitching grid. After stitching, altium_repour so the net's pours join through the vias, then altium_run_drc.",+   "pitfalls": [+    "Stitching keeps the pad clearance conservative (a pad's circumscribed circle) and does not look at pours: pours of other nets repour around the vias.",+    "Unplated holes are bare holes (pad = drill); plated holes take a ring and can join a net (e.g. GND)."+   ],+   "related": [+    "place_vias",+    "repour",+    "run_drc",+    "routing_state"+   ],+   "example": {+    "fiducials": [+     {+      "x": 5,+      "y": 5+     },+     {+      "x": 45,+      "y": 25+     }+    ],+    "mountingHoles": {+     "corners": {+      "inset": 4+     },+     "drill": 3.2+    },+    "stitching": {+     "net": "GND",+     "pitch": 4+    }+   },+   "longRunning": false,+   "timeoutSeconds": 180+  },+  {+   "name": "board_report",+   "tier": "checks",+   "summary": "One shareable page per board: every check run against the live board and its project (Altium's DRC, JLCPCB fab limits and assembly tiers, the schematic review, symbols against footprints, impedance widths, return paths, copper balance, 3D heights) with a picture of the board, written as a single HTML file.",+   "input": {+    "out": "the .html to write (default '<board> report.html' beside the project)",+    "pcbPath": "its .PcbDoc",+    "projectPath": "the .PrjPcb"+   },+   "output": {+    "elapsedSec": "number",+    "needsWork": "int",+    "pass": "int",+    "picture": "bool",+    "report": "path",+    "sections": "[{title, ok (true|false|null = info), summary}]"+   },+   "hint": "Open the file in a browser or send it; each section names the verb to run for its full detail.",+   "pitfalls": [+    "Runs Altium's full batch DRC and frames the board in Altium's window for the picture (no focus taken).",+    "Takes about a minute on a small board."+   ],+   "related": [+    "fab_check",+    "assembly_check",+    "schematic_review",+    "pin_pad_check",+    "return_path",+    "copper_balance",+    "height_check",+    "routing_drc"+   ],+   "example": {+    "pcbPath": "C:/proj/Board.PcbDoc",+    "projectPath": "C:/proj/Board.PrjPcb"+   },+   "longRunning": false,+   "timeoutSeconds": 900+  },+  {+   "name": "bom",+   "tier": "checks",+   "summary": "The project's bill of materials read straight from Altium: every schematic sheet the .PrjPcb lists (placed parts and their parameters) merged with the board (footprint, position, side), a variant applied, grouped one line per manufacturer part number, checked for what an order needs, and written as bom.csv, bom-jlcpcb.csv + cpl-jlcpcb.csv, bom-mouser.csv and bom-digikey.csv (+ bom.json).",+   "input": {+    "boards": "int, default 1 - multiplies the quantities in the Mouser/DigiKey files",+    "outputDir": "path - default <project folder>\\BOM",+    "pcbPath": "path - optional, default the project's board",+    "projectPath": "path to the .PrjPcb (required)",+    "timeout": "seconds per sheet read, default 120",+    "variant": "string - optional, a project variant's description (Not Fitted parts become DNP, alternates are listed)",+    "write": "bool, default true"+   },+   "output": {+    "alternates": "[{designator, alternatePart}]",+    "checks": "{missingMpn, missingManufacturer, missingPartNumbers{mouser, digikey, lcsc}, inconsistent[{field, designators, values}], dnp, excludedKinds, sheetNotOnBoard, boardNotOnSheet}",+    "files": "[path]",+    "fittedParts": "int",+    "lines": "[{quantity, designators, value, comment, description, footprint, package, mpn, manufacturer, mouser, digikey, lcsc, libReference}]",+    "variants": "[string]"+   },+   "hint": "Parameter names are matched across libraries (MPN = Manufacturer Part Number = Mfr Part #; Mouser Part # / Digi-Key Part Number; LCSC Part # / JLCPCB Part #). Missing numbers: look them up (adom-parts-search search <MPN>) and write them back with altium_sheet_params, then run this again.",+   "pitfalls": [+    "Graphical, Net Tie (no BOM) and Standard (no BOM) components are left out (checks.excludedKinds).",+    "DNP: a variant's Not Fitted parts, or a DNP / Do Not Populate / Fitted=false parameter.",+    "Variants are read from the .PrjPcb text; alternate parts are listed, not substituted.",+    "CPL rotations are Altium's: JLCPCB's preview may need per-footprint corrections.",+    "A multi-channel (repeated) sheet is read once: its copies' designators land in checks.boardNotOnSheet.",+    "Only the parameters a BOM needs are read (some libraries carry huge blob parameters); a workspace-managed part's description comes from a Description parameter, never its vault."+   ],+   "related": [+    "sheet_params",+    "export_manufacturing",+    "read_schematic"+   ],+   "example": {+    "boards": 10,+    "projectPath": "C:/proj/board.PrjPcb"+   },+   "longRunning": false,+   "timeoutSeconds": 600+  },+  {+   "name": "build_board",+   "tier": "checks",+   "summary": "Build a NEW board from a parts list and a netlist: outline (rounded corners allowed), every footprint from one PcbLib placed by its own origin, pad nets assigned, saved; then read back pin by pin.",+   "input": {+    "libraryPath": "string (required) - the .PcbLib holding every footprint",+    "nets": "{net: [\"R1.1\", \"U1.3\", ...]}",+    "outline": "{rect:[x0,y0,w,h], cornerRadius?} or {points:[[x,y]...]} (mm)",+    "parts": "[{ref, footprint, x, y, rotation, comment?}] (mm, degrees; x,y is the footprint's own origin)",+    "pcbPath": "string (required) - the .PcbDoc to write"+   },+   "output": {+    "counts": "{...}",+    "missingParts": "[ref]",+    "padsWired": "int",+    "placed": "int",+    "revision": "string",+    "unconnected": "int - the ratsnest to route",+    "wrongPins": "[{pin, want, got}]"+   },+   "hint": "The board AI Flow's Altium project builds from its netlist. Footprint pad names must match the netlist's pin numbers (R1.1 = pad 1 of R1). Coordinates are Altium's frame (y up).",+   "pitfalls": [+    "The library must open in this Altium; a footprint missing from it refuses the whole build (footprint_not_found).",+    "A new board each call: pcbPath is overwritten."+   ],+   "related": [+    "routing_state",+    "set_outline",+    "install_library"+   ],+   "example": {+    "libraryPath": "C:/designs/buck/Buck.PcbLib",+    "nets": {+     "GND": [+      "U1.1"+     ]+    },+    "outline": {+     "cornerRadius": 1,+     "rect": [+      0,+      0,+      28,+      20+     ]+    },+    "parts": [+     {+      "footprint": "SOT-23-6",+      "ref": "U1",+      "rotation": 0,+      "x": 12,+      "y": 10+     }+    ],+    "pcbPath": "C:/designs/buck/buck.PcbDoc"+   },+   "longRunning": false,+   "timeoutSeconds": 900+  },+  {+   "name": "build_footprint",+   "tier": "checks",+   "summary": "Build a complete footprint (pads, silkscreen, courtyard, arcs) in a PcbLib from a kicad2altium spec file on this machine, and return a proof frame.",+   "input": {+    "libraryPath": "path to a .PcbLib (required; created if missing)",+    "spec": "Windows path to the spec (tools/kicad2altium.py output; mm, Y up) (required)",+    "timeout": "seconds, default 240"+   },+   "output": {+    "data": "{footprint, description, library, libraryState, pads, throughHolePads, drawingPrimitives}",+    "shot": "{image: base64 png, width, height}",+    "windowEtiquette": "background"+   },+   "hint": "Stage the spec with ab's send_files first. Then altium_add_body {step}, altium_plate_vias for thermal vias, altium_save. altium_import_part does all of it in one call.",+   "pitfalls": [+    "The footprint name comes from the spec's META line, not from an argument.",+    "Pads with a repeated designator are legal and all map to that pin (the heatsink-via convention).",+    "Library is left UNSAVED."+   ],+   "related": [+    "import_part",+    "add_body",+    "plate_vias",+    "show_footprint"+   ],+   "example": {+    "libraryPath": "C:/lib/Adom_ToF.PcbLib",+    "spec": "C:/lib/specs/VL53L5CX.fp.txt"+   },+   "longRunning": false,+   "timeoutSeconds": 240+  },+  {+   "name": "build_schematic",+   "tier": "checks",+   "summary": "Draw a design's schematic from its netlist with the project library's symbols, inside a new project (.PrjPcb with the board and libraries), compile it, and check its netlist pin by pin.",+   "input": {+    "drawnBy": "optional",+    "libraryPath": "string - the project .SchLib",+    "nets": "{net: [\"U1.3\", ...]} - pins of parts not on the sheet are ignored",+    "number": "optional",+    "parts": "[{ref, symbol: LibReference in the SchLib, block?: column name (input, core, output...), comment?: value, footprint?: its footprint in pcbLibPath (the part's PCB model link)}]",+    "pcbLibPath": "(optional)",+    "pcbPath": "(optional) the board, listed in the project",+    "projectPath": "(optional) .PrjPcb, default beside the sheet",+    "revision": "optional",+    "schPath": "string - the .SchDoc to write",+    "title": "optional"+   },+   "output": {+    "compileErrors": "[..]",+    "labels": "int",+    "mismatches": "[{net, missing, extra}]",+    "pinsWithoutNet": "ref.pin;...",+    "placed": "int",+    "project": "path",+    "sch": "path",+    "wires": "int"+   },+   "hint": "One column per block, each part with a wire stub and a net label on every connected pin; designator above, value below; title block filled. The compiled netlist is the proof: success means it matches the given nets.",+   "pitfalls": [+    "The SchLib must hold every symbol (altium_import_part).",+    "Parts are placed unrotated."+   ],+   "related": [+    "schematic_data",+    "compile",+    "build_board"+   ],+   "example": {+    "libraryPath": "C:\\work\\library\\Buck.SchLib",+    "nets": {+     "VIN": [+      "U1.3"+     ]+    },+    "parts": [+     {+      "block": "core",+      "comment": "TPS54202",+      "ref": "U1",+      "symbol": "TPS54202DDCR"+     }+    ],+    "schPath": "C:\\work\\Buck.SchDoc"+   },+   "longRunning": false,+   "timeoutSeconds": 900+  },+  {+   "name": "check_orientation",+   "tier": "checks",+   "summary": "Advisory: 2-pad parts whose rotation could be better. Each orthogonal rotation is scored in place by airline length (a routable net's MST with the pad there; a plane-like net's distance to its nearest pad); parts whose best rotation saves minImprovementMm are listed. Nothing is moved.",+   "input": {+    "filePath": "optional .PcbDoc",+    "minImprovementMm": "default 0.6",+    "parts": "[refs] - optional, default every 2-pad part",+    "planeNetPads": "a net with more pads than this is treated as a plane (nearest-pad distance), default 12"+   },+   "output": {+    "checked": "int",+    "suggestions": "[{ref, rotation, suggested, airlineNowMm, airlineThenMm, savesMm}]"+   },+   "hint": "Rotate with altium_move_parts {parts:[{ref, rotation}]} (position kept).",+   "pitfalls": [+    "Routed copper is not considered: on a routed board these are hints."+   ],+   "related": [+    "move_parts",+    "find_space",+    "routing_state"+   ],+   "example": {+    "minImprovementMm": 0.5+   },+   "longRunning": false,+   "timeoutSeconds": 120+  },+  {+   "name": "copper_balance",+   "tier": "checks",+   "summary": "How much of each copper layer is copper (tracks, pads, vias and what Altium actually poured, inside the outline; a plane counts full), and how far apart each mirrored pair of layers is: the copper balance a fab asks about because an unbalanced stack bows and twists in reflow.",+   "input": {+    "filePath": "the .PcbDoc (opened first; else the current board)",+    "resolutionMm": "raster cell, default 0.1"+   },+   "output": {+    "balanced": "bool",+    "boardMm2": "number",+    "layers": "[{layer, copperMm2, percent, plane?}]",+    "pairs": "[{layers, percent, differencePoints, ok}] - layer 1 with n, 2 with n-1...",+    "poursWithoutRegions": "[{net, layer}] - pours not poured, or hatched from tracks (counted through their tracks)"+   },+   "hint": "Pour the lighter layer of an unbalanced pair (altium_apply_pours: a GND pour, or copper thieving) or ask the fab for thieving; then run it again.",+   "pitfalls": [+    "A raster of the live board's copper: within a cell of the true area (0.1 mm default).",+    "A pour counts only once poured (regions); a hatched pour built from tracks is counted through its tracks only when the board lists them."+   ],+   "related": [+    "apply_pours",+    "pour_state",+    "layer_stack",+    "fab_check"+   ],+   "example": {},+   "longRunning": false,+   "timeoutSeconds": 180+  },+  {+   "name": "cross_check",+   "tier": "checks",+   "summary": "Compare one schematic sheet's designators with a board's. Altium holds ONE current document, so the verb activates the board, reads it, then activates the sheet and reads that.",+   "input": {+    "board": "path to the .PcbDoc - optional, else the current board",+    "sheet": "path to the .SchDoc - optional, else the current sheet",+    "timeout": "seconds per step, default 45"+   },+   "output": {+    "board": "path",+    "boardComponents": "int",+    "matched": "int",+    "onBoardNotOnSheet": "[designator]",+    "onSheetNotOnBoard": "[designator]",+    "sameProject": "bool | null - same folder",+    "schematicComponents": "int",+    "sheet": "string"+   },+   "hint": "Pass BOTH paths: with neither, one of the two reads has no current document and the call fails at stage board or sheet. Follow differences with altium_component_data / altium_schematic_data.",+   "pitfalls": [+    "The schematic side is ONE sheet: on a hierarchical design run it per leaf sheet before calling anything missing.",+    "_warning means sheet and board sit in different folders, probably different projects: every difference is then meaningless.",+    "With paths given it leaves the SHEET current; altium_activate_document the board again before board verbs."+   ],+   "related": [+    "read_board",+    "read_schematic",+    "review_board"+   ],+   "example": {+    "board": "C:/proj/board.PcbDoc",+    "sheet": "C:/proj/main.SchDoc"+   },+   "longRunning": false,+   "timeoutSeconds": 120+  },+  {+   "name": "current_capacity",+   "tier": "checks",+   "summary": "Will this trace carry the current? For each power net and its current: the narrowest track on every layer it is routed on against the width IPC-2221 needs for the allowed temperature rise (with each layer's real copper weight), and what one of its vias carries.",+   "input": {+    "filePath": "the .PcbDoc (opened first; else the current board)",+    "nets": "{net: amps}, e.g. {VIN: 3, GND: 3}",+    "platingUm": "via barrel plating, default 25",+    "riseC": "allowed temperature rise, default 10"+   },+   "output": {+    "nets": "[{net, amps, layers:[{layer, copperUm, narrowestMm, at, neededMm, ok}], shortOn, ok, vias:{count, smallestDrillMm, ampsEach, neededPerLayerChange}, poursOn}]",+    "ok": "bool"+   },+   "hint": "Widen what shortOn names (altium_set_track_width, altium_add_rule kind width) or pour the net (altium_apply_pours); a layer change needs neededPerLayerChange vias side by side (altium_place_vias).",+   "pitfalls": [+    "IPC-2221 (conservative, the chart fabs quote); IPC-2152 allows more on inner layers next to planes.",+    "Reads tracks and arcs: a pour on the same layer (poursOn) may already carry the current."+   ],+   "related": [+    "routing_state",+    "layer_stack",+    "set_track_width",+    "add_rule",+    "apply_pours",+    "place_vias"+   ],+   "example": {+    "nets": {+     "GND": 3,+     "SW": 3,+     "VIN": 3+    },+    "riseC": 10+   },+   "longRunning": false,+   "timeoutSeconds": 120+  },+  {+   "name": "fab_check",+   "tier": "checks",+   "summary": "Before ordering: the board's fab-relevant minimums measured from the live board (narrowest track, smallest via drill, diameter and annular ring, smallest pad hole and ring, smallest SMD pad, clearance rule, copper layers, board size), each with where it is; with a fab's limits, which ones fall short.",+   "input": {+    "copperOz": "outer copper weight for the preset (default: read from the stack)",+    "fab": "jlcpcb - JLCPCB's published standard capabilities for this board's layer count and copper weight (source and date in the reply)",+    "filePath": "the .PcbDoc (opened first)",+    "limits": "{minTrack, minClearance, minViaDrill, minViaDiameter, minViaAnnularRing, minPadHole, minPadAnnularRing, minSmdPad, minEdgeClearance, minHoleToHoleVia, minHoleToHolePad, maxDrill, minSilkLine, minSilkText, maxLayers, minBoardMm:[w,h], maxBoardMm:[w,h]} in mm - any fab's numbers; with fab, these override the preset"+   },+   "output": {+    "costlierOrBelowRecommended": "[{feature, boardMm, note}]",+    "fab": "{name, service, copperOz, source, read, notes} | null",+    "fits": "bool | null",+    "measured": "{track, viaDrill, viaDiameter, viaAnnularRing, padHole, padAnnularRing, smdPad, edgeClearance, holeToHoleVia, holeToHolePad, largestDrill, silkText, silkLine: {mm, at}, clearanceRule, copperLayers, boardSize, counts}",+    "outsideLimits": "[{limit, limitMm, boardMm, at}]"+   },+   "hint": "Look up the fab's own limits (its capability page) and pass them: the bridge does not carry fab numbers, they change. The measured values are what a fab's order form asks for.",+   "pitfalls": [+    "Measures the board as it is now, from the live document: routed copper, vias and pads.",+    "Board size is the outline's bounding box."+   ],+   "related": [+    "routing_state",+    "read_rules",+    "layer_stack",+    "export_manufacturing",+    "run_drc"+   ],+   "example": {+    "fab": "jlcpcb"+   },+   "longRunning": false,+   "timeoutSeconds": 180+  },+  {+   "name": "find_space",+   "tier": "checks",+   "summary": "Free spots on the board for a part near another (clear of every other part on that side, of that side's tracks and vias, and inside the outline), nearest first; or check:true lists parts that already overlap or leave the outline.",+   "input": {+    "check": "bool - report overlaps instead",+    "clearanceMm": "default 0.25",+    "copper": "bool, default true - avoid that side's tracks and vias",+    "count": "default 5",+    "edgeMm": "from the outline, default 0.3",+    "near": "designator to place beside (or at: {x, y} mm)",+    "part": "designator of the part to place (its own size and origin), or sizeMm: [w, h]",+    "precise": "bool, with check - also run Altium's batch DRC and list its Component Clearance violations (primitive to primitive)",+    "radiusMm": "search radius, default 10",+    "side": "top | bottom (default the part's)"+   },+   "output": {+    "outsideOutline": "[designator] (check)",+    "overlaps": "[[a, b]] (check)",+    "spots": "[{x, y (the part origin, for move_parts), distanceMm, extent}]"+   },+   "hint": "Move the part with altium_move_parts {parts:[{ref, x, y, rotation}]} to a spot, then altium_routing_drc. Extents follow Altium's Component Clearance rule: 3D bodies and pads, or every primitive but text for a part with no body.",+   "pitfalls": [+    "A part with no 3D body is measured by its silkscreen outline too, as Altium does: dense boards report silk-to-silk overlaps.",+    "Rotations are kept: rotate first if the part should sit another way."+   ],+   "related": [+    "move_parts",+    "routing_state",+    "routing_drc"+   ],+   "example": {+    "clearanceMm": 0.3,+    "near": "U1",+    "part": "C10"+   },+   "longRunning": false,+   "timeoutSeconds": 180+  },+  {+   "name": "footprint_from_land_pattern",+   "tier": "checks",+   "summary": "Build a complete footprint from the land pattern a datasheet prints: pads numbered the standard way (chip, dual SOIC/TSSOP/DFN, SOT, quad QFP/QFN, BGA), an exposed pad, the courtyard, silkscreen kept clear of the copper with a pin-1 dot, a fab outline with a pin-1 chamfer, and an extruded 3D body when the height is given.",+   "input": {+    "ballPad": "bga pad diameter mm",+    "body": "{w, l, height?, standoff?} mm - silkscreen, fab, courtyard, 3D",+    "cols": "bga",+    "courtyard": "margin mm, default 0.25",+    "description": "optional",+    "dryRun": "bool - return the pad table and spec only",+    "exposedPad": "{w, h, x?, y?, number?}",+    "family": "chip | dual | sot | quad | bga",+    "leftPins": "sot",+    "libraryPath": ".PcbLib (required unless dryRun; created if missing)",+    "missing": "bga ['B2', ...]",+    "name": "footprint name (required)",+    "padLength": "mm, along the lead (across the row)",+    "padShape": "roundrect (default) | rect | round | oval",+    "padWidth": "mm, along the row",+    "pins": "dual/quad total",+    "pinsPerSideX": "quad, top/bottom",+    "pinsPerSideY": "quad, left/right",+    "pitch": "mm",+    "polarized": "chip: add the pin-1 dot",+    "rightPins": "sot",+    "rows": "bga",+    "silkClearance": "default 0.2",+    "silkWidth": "default 0.15",+    "span": "mm, pad centre to pad centre across the rows (quad: left-right)",+    "spanY": "quad, top-bottom (default span)"+   },+   "output": {+    "body3d": "{success}",+    "shot": "{image}",+    "spec": "the staged spec path",+    "summary": "{pads, courtyardMm, silkLines, padTable:[[name, x, y, w, h]]}"+   },+   "hint": "dryRun first and compare padTable with the datasheet drawing; then build, check shot.image, altium_save the library and link it with altium_set_models.",+   "pitfalls": [+    "The numbers are the datasheet's RECOMMENDED LAND PATTERN, not the package outline: pad size and the pad-centre span.",+    "Pin 1 is top-left (Y up), counter-clockwise, the IPC convention."+   ],+   "related": [+    "build_footprint",+    "create_footprint",+    "add_body",+    "set_models",+    "save",+    "show_footprint"+   ],+   "example": {+    "body": {+     "height": 1.75,+     "l": 4.9,+     "w": 3.9+    },+    "family": "dual",+    "libraryPath": "C:\\\\lib\\\\Parts.PcbLib",+    "name": "SOIC-8_3.9x4.9mm_P1.27mm",+    "padLength": 1.55,+    "padWidth": 0.6,+    "pins": 8,+    "pitch": 1.27,+    "span": 5.4+   },+   "longRunning": false,+   "timeoutSeconds": 300+  },+  {+   "name": "height_check",+   "tier": "checks",+   "summary": "Will it fit the enclosure? Each part's height from its 3D bodies (top and bottom side), the parts over a height limit board-wide or under a lid feature in a region, and part bodies that collide or come closer than a gap.",+   "input": {+    "filePath": "the .PcbDoc (opened first; else the current board)",+    "limit": "how many of the tallest parts to list (default 25)",+    "maxBottomMm": "limit below the bottom side",+    "maxTopMm": "height limit above the top side",+    "minGapMm": "report bodies closer than this (default 0: collisions only)",+    "regions": "[{rect:[x1,y1,x2,y2] mm, maxMm, side?: top|bottom, name?}] - lid features over part of the board"+   },+   "output": {+    "collisions": "[{parts, overlapMm, at}]",+    "heights": "[{ref, side, heightMm, rect}]",+    "noBody": "[ref] - parts with no 3D body (no height)",+    "ok": "bool",+    "overLimit": "[{ref, side, heightMm, limitMm, overByMm}]",+    "regionHits": "[{region, ref, heightMm, maxMm}]",+    "tallest": "{top, bottom}",+    "tooClose": "[{parts, gapMm, at}]"+   },+   "hint": "Move or swap what overLimit and regionHits name (altium_move_parts, altium_swap_footprint); separate colliding parts. Parts in noBody need a body (altium_add_body) before the check means anything for them.",+   "pitfalls": [+    "Bodies are compared as boxes, each body its own box; Altium's Component Clearance DRC checks the real shapes.",+    "A height is the body's top above the surface its part sits on; the board's own thickness is not included."+   ],+   "related": [+    "add_body",+    "move_parts",+    "find_space",+    "run_drc",+    "show_3d_board"+   ],+   "example": {+    "maxBottomMm": 2,+    "maxTopMm": 8,+    "minGapMm": 0.25,+    "regions": [+     {+      "maxMm": 3,+      "name": "display boss",+      "rect": [+       10,+       10,+       30,+       25+      ]+     }+    ]+   },+   "longRunning": false,+   "timeoutSeconds": 120+  },+  {+   "name": "impedance",+   "tier": "checks",+   "summary": "Controlled impedance from the board's own stack-up: the track width each copper layer needs for a single-ended target (50 ohm default) and the width and gap for a differential pair (90 ohm USB, 100 ohm Ethernet/LVDS), microstrip outside and stripline inside; optionally written as width or diff-pair rules per layer.",+   "input": {+    "apply": "{scope: Altium query e.g. InNetClass('RF') or InDifferentialPair('USB'), name?} - one rule per layer at +/-10%",+    "diff": "differential ohm, e.g. 90 or 100",+    "filePath": "the .PcbDoc (opened first; else the current board)",+    "gapMm": "fix the pair gap (default: gap = width)",+    "layers": "[layer names] (default every copper layer)",+    "z0": "single-ended ohm (default 50 when diff is not given)"+   },+   "output": {+    "applied": "[{layer, rule, success}] | null",+    "layers": "[{layer, model, copperUm, dielectricMm:[above, below], width, z0, diffWidth, diffGap, zdiff}]"+   },+   "hint": "Then altium_add_rule or apply writes the rules; altium_diff_pairs lists the pairs to scope them to. Tell the fab the target impedance: it tunes the stack-up with its own solver.",+   "pitfalls": [+    "Closed-form models (Hammerstad-Jensen, Wheeler, IPC-2141 coupling): within a few percent of a field solver on FR-4; uncoated (mask lowers outer layers 1 to 3 ohm).",+    "Inner layers assume planes on both neighbours."+   ],+   "related": [+    "layer_stack",+    "add_rule",+    "diff_pairs",+    "net_classes"+   ],+   "example": {+    "apply": {+     "name": "USB 90",+     "scope": "InDifferentialPair('USB')"+    },+    "diff": 90,+    "z0": 50+   },+   "longRunning": false,+   "timeoutSeconds": 120+  },+  {+   "name": "import_part",+   "tier": "checks",+   "summary": "One call imports a whole part: footprint spec + STEP + symbol modes + models + params + save.",+   "input": {+    "comment": "string",+    "description": "string",+    "footprintSpec": "path",+    "params": "{}",+    "pcbLib": "path",+    "schLib": "path",+    "step": "path",+    "symbolSpec": "path"+   },+   "output": {+    "footprint": "string",+    "steps": "[] - the add_body step carries replacedBodies, bodiesNow, sameNameFootprints",+    "symbol": "string"+   },+   "hint": "Check the step counts: models:0 fails the call - a symbol with no footprint model places with nothing attached. Importing again REPLACES the symbol and the footprint's 3D body (add_body step: bodiesNow 1); boards that place the part need PCB:UpdateFootprints.",+   "pitfalls": [+    "add_models APPENDS on every call (not idempotent) - repair is remove_symbol + re-import.",+    "The add_body step's sameNameFootprints > 1 means the library holds stale footprints of that name: the body went to the FIRST only.",+    "The first part into a NEW library can silently skip models+params if the bridge restarted mid-flight - verify with read_symbol."+   ],+   "related": [+    "read_symbol",+    "show_symbol"+   ],+   "example": {},+   "longRunning": false,+   "timeoutSeconds": 300+  },+  {+   "name": "jlcpcb_order",+   "tier": "checks",+   "summary": "A JLCPCB order in one call: the board checked against JLCPCB's fab limits, the Gerbers and drill zipped the way JLCPCB's order page takes them, the JLCPCB BOM and pick-and-place (CPL), the assembly-tier check, and the order-form values, in one folder with every issue in one list.",+   "input": {+    "outputDir": "the order folder (default 'JLCPCB order <board>' beside the project; emptied first)",+    "pcbPath": "its .PcbDoc",+    "projectPath": "the .PrjPcb",+    "variant": "an assembly variant for the BOM (optional)"+   },+   "output": {+    "assemblyService": "economic | standard | null",+    "bomFiles": "[..] incl. bom-jlcpcb.csv, cpl-jlcpcb.csv",+    "folder": "path",+    "gerberZip": "{path, files}",+    "issues": "[{from, what, at?}]",+    "orderForm": "{layers, dimensionsMm, outerCopperOz, minTrackSpaceMm, minHoleMm}",+    "ready": "bool - fab fits, an assembly tier passes, no issues"+   },+   "hint": "Fix what issues lists and run it again; when ready, upload the zip, then the BOM and CPL for assembly and check rotations in JLCPCB's preview.",+   "pitfalls": [+    "Runs Silkscreen Preparation on the board (through export_manufacturing) and writes the order folder fresh each time.",+    "Takes a minute or two: the manufacturing export is most of it."+   ],+   "related": [+    "fab_check",+    "assembly_check",+    "export_manufacturing",+    "bom",+    "panelize"+   ],+   "example": {+    "pcbPath": "C:/proj/Board.PcbDoc",+    "projectPath": "C:/proj/Board.PrjPcb"+   },+   "longRunning": false,+   "timeoutSeconds": 900+  },+  {+   "name": "length_tune",+   "tier": "checks",+   "summary": "Lengthen a routed net to a target length, or to match another net (a pair's skew, a DDR byte lane): a serpentine in its longest straight track that has room beside it, the bumps sized to add exactly the length missing, every bump clear of other copper and inside the board.",+   "input": {+    "clearance": "to other copper, default 0.2",+    "dryRun": "bool - plan only",+    "expectedRevision": "optional",+    "filePath": "the .PcbDoc (opened first; else the current board)",+    "matchTo": "or: a net whose length to match",+    "maxAmplitudeMm": "bump height limit, default 3",+    "net": "the net to lengthen",+    "pitchMm": "one bump's length along the track (default 4 x width, at least 4 x clearance + 2 x width)",+    "targetMm": "the length it should have"+   },+   "output": {+    "changed": "bool",+    "errorMm": "number",+    "lengthBeforeMm": "number",+    "lengthMm": "number",+    "plan": "{layer, replaces, bumps, amplitudeMm, pitchMm, side, addsMm, points}",+    "targetMm": "number"+   },+   "hint": "Then altium_net_lengths (the pair or group), altium_routing_drc. For a matched group, tune each short net to the longest (matchTo).",+   "pitfalls": [+    "Only lengthens: a net already longer than its target is left alone.",+    "One serpentine per call, in one track; call again to add more elsewhere."+   ],+   "related": [+    "net_lengths",+    "route_pair",+    "add_rule",+    "routing_drc"+   ],+   "example": {+    "matchTo": "DP",+    "net": "DN"+   },+   "longRunning": false,+   "timeoutSeconds": 120+  },+  {+   "name": "net_lengths",+   "tier": "checks",+   "summary": "Routed length of every net (or the ones named) on the live board, by layer with its vias; the skew inside each differential pair; and the spread of matched-length groups against a tolerance.",+   "input": {+    "filePath": "the .PcbDoc (opened first)",+    "groups": "[{nets:[..], toleranceMm?: 0.5}] - matched-length groups (a DDR byte lane, a parallel bus)",+    "nets": "[net] - default every routed net",+    "pairs": "[{name?, pos, neg}] - default the board's differential pairs"+   },+   "output": {+    "groups": "[{nets:[{net, lengthMm, shortByMm}], spreadMm, withinTolerance, longest}]",+    "groupsOutOfTolerance": "int",+    "nets": "[{net, lengthMm, byLayer, vias, segments}]",+    "pairs": "[{name, pos, neg, posMm, negMm, skewMm}]"+   },+   "hint": "Lengthen the short nets of a group with altium_route_net (a detour) until spreadMm is within tolerance; Altium's interactive length tuning is the hand alternative.",+   "pitfalls": [+    "A net with branches sums every branch: a multi-pin net's length is its total copper, not pin to pin.",+    "Vias are counted, not added to the length."+   ],+   "related": [+    "routing_state",+    "route_net",+    "diff_pairs",+    "add_rule"+   ],+   "example": {+    "groups": [+     {+      "nets": [+       "D0",+       "D1",+       "D2",+       "D3"+      ],+      "toleranceMm": 0.25+     }+    ]+   },+   "longRunning": false,+   "timeoutSeconds": 180+  },+  {+   "name": "pin_pad_check",+   "tier": "checks",+   "summary": "Do the footprints fit their symbols? Part by part, the symbol's pin numbers (from the sheet files) against the footprint's pad names (from the board): pins with no pad, pads with no pin, and parts on only the sheets or only the board.",+   "input": {+    "pcbPath": "its .PcbDoc",+    "projectPath": "the .PrjPcb"+   },+   "output": {+    "matching": "int",+    "mismatches": "[{ref, pinsWithoutPad, padsWithoutPin, pins, pads}]",+    "ok": "bool - every pin has a pad",+    "onBoardOnly": "[ref]",+    "onSheetsOnly": "[ref]"+   },+   "hint": "A pin with no pad is a net that can never reach copper: change the footprint (altium_swap_footprint) or renumber the symbol's pins before routing.",+   "pitfalls": [+    "Mounting and thermal pads (MH*, MP*, SH*, EP*, PAD*, TAB*, unnamed or 0) are expected to have no pin."+   ],+   "related": [+    "schematic_review",+    "update_pcb",+    "swap_footprint",+    "component_pins"+   ],+   "example": {+    "pcbPath": "C:/proj/Board.PcbDoc",+    "projectPath": "C:/proj/Board.PrjPcb"+   },+   "longRunning": false,+   "timeoutSeconds": 200+  },+  {+   "name": "placement_check",+   "tier": "checks",+   "summary": "Review the placement before routing: parts whose courtyards (pads and body, grown by a margin) overlap on the same side, parts off the board or over its edge, pads too close to the edge for assembly and depanelling, and parts on a mounting hole.",+   "input": {+    "edgeMm": "pad to board edge, default 0.5",+    "filePath": "the .PcbDoc (opened first; else the current board)",+    "marginMm": "courtyard margin round each part, default 0.1 (parts closer than 0.2 mm are flagged)"+   },+   "output": {+    "courtyardOverlaps": "[{parts, side, overlapMm}]",+    "nearEdge": "[{ref, padToEdgeMm}]",+    "offOrOverEdge": "[{ref, where}]",+    "ok": "bool",+    "onMountingHoles": "[{ref, hole}]",+    "parts": "int"+   },+   "hint": "Move what it names (altium_move_parts, altium_find_space for a clear spot), then route.",+   "pitfalls": [+    "A part's box is its pads and its 3D body, so an L-shaped part reads as its bounding box; on or off the board is judged by its pads."+   ],+   "related": [+    "move_parts",+    "find_space",+    "height_check",+    "board_features"+   ],+   "example": {+    "marginMm": 0.25+   },+   "longRunning": false,+   "timeoutSeconds": 180+  },+  {+   "name": "read_sheet_file",+   "tier": "checks",+   "summary": "Read a schematic sheet straight from its .SchDoc file, without Altium (it need not be installed or running): every part with its designator, library reference, comment, UniqueID, part number, position, parameters and pins, in milliseconds.",+   "input": {+    "params": "bool, default false - every parameter with its value",+    "path": "the .SchDoc (alias filePath)",+    "pins": "bool, default true - each part's own pins plus the shared part 0"+   },+   "output": {+    "components": "[{designator, libReference, comment, description, uniqueId, partId, partCount, xMil, yMil, orientation, sourceLibrary, params?, pins?}]",+    "count": "int",+    "elapsedMs": "int",+    "format": "the file header"+   },+   "hint": "The saved file, not the live sheet: an open Altium's unsaved edits are not in it (altium_schematic_data reads those). Useful when Altium is closed, busy or slow, and to check UniqueIDs before Update PCB.",+   "pitfalls": [+    "Binary .SchDoc only (Altium's default); an ASCII-format sheet is refused by name.",+    "No connectivity: nets come from altium_netlist."+   ],+   "related": [+    "schematic_data",+    "part_parameters",+    "bom",+    "netlist"+   ],+   "example": {+    "params": true,+    "path": "C:\\\\work\\\\Power.SchDoc"+   },+   "longRunning": false,+   "timeoutSeconds": 30+  },+  {+   "name": "return_path",+   "tier": "checks",+   "summary": "Where signal tracks lose their reference: for every signal net, the length of track on each layer that runs over no pour or plane copper on either neighbouring layer (a split, a gap, a void), with where the first gap starts. The return current detours around those gaps: loop area, emissions, crosstalk.",+   "input": {+    "filePath": "the .PcbDoc (opened first; else the current board)",+    "nets": "[net] - only these (default every signal net)",+    "referenceNets": "[net] - the reference nets (default: every net that owns a pour or a plane)",+    "resolutionMm": "raster cell and sampling step, default 0.1"+   },+   "output": {+    "layersWithNoReferenceCopper": "[layer]",+    "nets": "[{net, layer, lengthMm, unreferencedMm, percent, firstGapAt}] worst first",+    "ok": "bool",+    "referenceNets": "[net]"+   },+   "hint": "Extend or join the pour under the net (altium_apply_pours), reroute it over solid copper (altium_route_net), or add a stitching via or capacitor where it changes reference.",+   "pitfalls": [+    "Reference copper is pours, planes, the reference nets' own copper and net-less copper (a hatched pour's tracks): another signal's track under a net is not a reference.",+    "Checks the two neighbouring copper layers; a board with no pours or planes has nothing to reference (layersWithNoReferenceCopper)."+   ],+   "related": [+    "apply_pours",+    "pour_state",+    "route_net",+    "net_lengths",+    "board_features"+   ],+   "example": {+    "nets": [+     "USB_DP",+     "USB_DN",+     "CLK"+    ]+   },+   "longRunning": false,+   "timeoutSeconds": 180+  },+  {+   "name": "review_board",+   "tier": "checks",+   "summary": "Design-review checks (14) over one board read; schematic checks with includeSchematic.",+   "input": {+    "board": "path - optional",+    "checks": "[string] - optional filter",+    "includeSchematic": "bool",+    "sheet": "path - optional"+   },+   "output": {+    "counts": "{}",+    "findings": "[{check, severity, message, ...}]",+    "notImplemented": "{}",+    "skipped": "{}"+   },+   "hint": "PARTIAL results by design: skipped says which checks had no data; a clean result means the checks that RAN found nothing.",+   "pitfalls": [+    "Altium holds ONE current document - the review activates board and sheet in turn."+   ],+   "related": [+    "read_board",+    "cross_check"+   ],+   "example": {+    "includeSchematic": true+   },+   "longRunning": false,+   "timeoutSeconds": 120+  },+  {+   "name": "route_pair",+   "tier": "checks",+   "summary": "Route a differential pair as a pair: one centreline from the source pads' midpoint through waypoints to the destination pads' midpoint, each net offset to its own side at width + gap with mitred corners and a short fan-out from each pad; width and gap given or solved from the stack-up for a target impedance; both lengths and the skew answered.",+   "input": {+    "clearance": "to other nets' pads when checking the plan, default 0.15",+    "diffOhms": "solve width (and gap = width unless given) for this differential impedance on the layer",+    "fanMm": "fan-out length at each end, default 1",+    "force": "bool - route even when the plan check finds problems",+    "from": "{pos: \"U1.3\", neg: \"U1.4\"} the source pads",+    "gap": "mm",+    "layer": "F.Cu (default) | B.Cu | In<n>.Cu",+    "neg": "the negative net",+    "pos": "the positive net",+    "rule": "bool, default true - add a width rule for the two nets (+/-10%) so the board's own width rule does not flag the pair",+    "to": "{pos, neg} the destination pads",+    "via": "[[x,y]...] centreline waypoints mm (optional)",+    "width": "mm"+   },+   "output": {+    "gapMm": "number",+    "lengthsMm": "{pos, neg}",+    "revision": "string",+    "sizedFor": "the impedance row used | null",+    "skewMm": "number",+    "widthMm": "number"+   },+   "hint": "Then altium_net_lengths for the routed skew, altium_add_rule {kind:\"diffpair\"} to hold width and gap, and altium_routing_drc.",+   "pitfalls": [+    "One layer per call; split a pair that changes layer into two calls at the via pair.",+    "The waypoints are the pair's centreline, so keep them clear of other copper by half the pair's width."+   ],+   "related": [+    "route_net",+    "impedance",+    "diff_pairs",+    "net_lengths",+    "add_rule"+   ],+   "example": {+    "diffOhms": 90,+    "from": {+     "neg": "U1.15",+     "pos": "U1.14"+    },+    "neg": "USB_DN",+    "pos": "USB_DP",+    "to": {+     "neg": "J1.2",+     "pos": "J1.3"+    },+    "via": [+     [+      20,+      10+     ],+     [+      30,+      10+     ]+    ]+   },+   "longRunning": false,+   "timeoutSeconds": 120+  },+  {+   "name": "schematic_review",+   "tier": "checks",+   "summary": "Review the schematic before Update PCB: every placed pin (read from the sheet files, with its electrical type) against Altium's compiled netlist. Errors: power pins on no net, outputs driving each other. Warnings: pins on no net, one-pin nets, inputs nothing drives, IC supplies with no decoupling capacitor to ground.",+   "input": {+    "projectPath": "the .PrjPcb (its sheets are read from file; the netlist is Altium's compile)"+   },+   "output": {+    "decoupling": "[{net, ics, capacitors, ok}]",+    "errors": "[{check, pin|net, ...}]",+    "nets": "int",+    "ok": "bool - no errors",+    "pins": "int",+    "sheets": "[path]",+    "warnings": "[{check, ...}]"+   },+   "hint": "Fix on the sheets (altium_sch_connect, altium_sch_place_part for a missing capacitor), then altium_update_pcb. altium_run_erc is Altium's own ERC with its rules.",+   "pitfalls": [+    "Pins named NC, N/C or DNC are expected to float.",+    "Ground nets are recognised by name (GND, AGND, DGND, PGND, VSS, 0V, *_GND)."+   ],+   "related": [+    "netlist",+    "read_sheet_file",+    "run_erc",+    "sch_connect",+    "update_pcb"+   ],+   "example": {+    "projectPath": "C:/proj/Board.PrjPcb"+   },+   "longRunning": false,+   "timeoutSeconds": 300+  },+  {+   "name": "silk_designators",+   "tier": "checks",+   "summary": "Place every reference designator the way assembly houses want: one size, upright, beside its own part, clear of every pad (JLCPCB: 0.15 mm), every other part's body and the other designators, inside the board; read back from Altium's own text boxes. Parts with no clear spot are reported, not forced onto pads.",+   "input": {+    "dryRun": "bool - plan only",+    "filePath": "the .PcbDoc (opened first; else the current board)",+    "gap": "label to part, mm, default 0.2",+    "minSize": "mm - parts with no spot at size step down by 0.1 to this (default: size, no shrinking)",+    "padClearance": "label to any pad, mm, default 0.15",+    "refs": "[ref] - only these (default every part)",+    "size": "text height mm, default 1.0 (JLCPCB's minimum)"+   },+   "output": {+    "onPadsAfter": "[{ref, pad}] - must be empty",+    "onPadsBefore": "[{ref, pad}]",+    "placed": "int",+    "planned": "dryRun: [{ref, x, y, size, rotation, side, at}]",+    "unplaced": "[{ref, why}]"+   },+   "hint": "Then altium_silkscreen_prep (it clips silk off the mask) and altium_fab_check {fab:\"jlcpcb\"} for text height and line width. For unplaced parts try refs with a smaller size, or move the part.",+   "pitfalls": [+    "Labels are tried above, below, right, left and at the corners of the part's body and pads, big parts first.",+    "Bottom-side labels are mirrored by Altium; onPadsAfter checks them with Altium's own boxes."+   ],+   "related": [+    "silk_state",+    "apply_silkscreen",+    "silkscreen_prep",+    "fab_check"+   ],+   "example": {+    "size": 1.0+   },+   "longRunning": false,+   "timeoutSeconds": 600+  },+  {+   "name": "test_board",+   "tier": "checks",+   "summary": "Place one footprint, or a ';' list, from the open PcbLib on ONE board: a NEW board (wMm x hMm outline) when pcbPath is new or omitted, APPENDED to the existing board when pcbPath already exists (issue 11).",+   "input": {+    "assigns": "des=NET;...",+    "footprintName": "string | 'A;B;C'",+    "hMm": "number (new board only)",+    "libraryPath": "path (.PcbLib)",+    "pcbPath": "path (.PcbDoc) - existing = append, new = create+save",+    "stubs": "des=x,y;...",+    "wMm": "number (new board only)"+   },+   "output": {+    "appended": "bool",+    "bodiesOnComp": "int",+    "count": "int",+    "existingParts": "int",+    "padsOnComp": "int",+    "pcb": "path",+    "placed": "string",+    "saved": "bool (checked on disk)"+   },+   "hint": "Three parts on one board: footprintName 'VL53L5CX;VL53L7CX;VL53L8CX' + pcbPath, then altium_show_3d_board {filePath: pcbPath}. Run it again with the same pcbPath to ADD parts: they land right of the current ones, designators continue (U4...).",+   "pitfalls": [+    "assigns/stubs apply to the LAST part placed.",+    "pcb is empty when pcbPath is omitted: the board exists only unsaved as PCB<n>.PcbDoc.",+    "Appending ignores wMm/hMm and does not grow the outline: parts can land outside it - resize with altium_board_shape if needed.",+    "Each part's name is its own Comment (moves with the part) and its Pattern is the footprint name.",+    "errorCode not_saved: the board was built but the file did not land; the folder is created automatically."+   ],+   "related": [+    "place_on_sch",+    "show_3d_board",+    "add_body"+   ],+   "example": {+    "footprintName": "VL53L5CX;VL53L7CX;VL53L8CX",+    "hMm": 14,+    "libraryPath": "C:/lib/Adom_ToF.PcbLib",+    "pcbPath": "C:/proj/tof.PcbDoc",+    "wMm": 39+   },+   "longRunning": false,+   "timeoutSeconds": 240+  },+  {+   "name": "thermal_vias",+   "tier": "checks",+   "summary": "A thermal via grid in a part's exposed pad (the ground or thermal pad under a QFN, DFN, PowerPAD or regulator): centred, inset from the pad's edge, sized by the board's Routing Via rule, on the pad's net.",+   "input": {+    "drill": "mm (default the board's via rule)",+    "dryRun": "bool - plan only",+    "expectedRevision": "optional",+    "filePath": "the .PcbDoc (opened first; else the current board)",+    "insetMm": "from the pad's edge, default 0.1",+    "pad": "the pad's name (default the part's largest pad)",+    "pitchMm": "default via size + 0.5, at least 1.0",+    "ref": "the part (e.g. U3)",+    "size": "via diameter mm (default the board's via rule)"+   },+   "output": {+    "applied": "int",+    "failed": "int",+    "planned": "{part, pad, net, padMm, vias, pitchMm, via, points}"+   },+   "hint": "Pour the pad's net on the far side (altium_apply_pours) so the heat has somewhere to go, ask the fab to tent or plug vias in pads, then altium_routing_drc. adom-fields solves the heat itself.",+   "pitfalls": [+    "Open vias in a pad wick solder away from it during reflow: tent or plug them, or keep drills at 0.3 mm or smaller."+   ],+   "related": [+    "apply_pours",+    "board_features",+    "routing_drc",+    "current_capacity"+   ],+   "example": {+    "pitchMm": 1.1,+    "ref": "U3"+   },+   "longRunning": false,+   "timeoutSeconds": 120+  },+  {+   "name": "via_fence",+   "tier": "checks",+   "summary": "A via fence for EMC: a row of ground vias on both sides of RF or fast nets' tracks, and/or round the board edge, at a pitch from the frequency (lambda/20) or given; each via clear of other copper and pads, the same net's vias and the edge.",+   "input": {+    "clearance": "to other copper, mm, default 0.2",+    "drill": "mm, default 0.3",+    "dryRun": "bool - plan only",+    "edge": "bool - a fence round the board edge too",+    "edgeInsetMm": "edge fence: distance in from the edge (default via radius + 0.5)",+    "expectedRevision": "optional",+    "filePath": "the .PcbDoc (opened first; else the current board)",+    "freqGHz": "the highest frequency: pitch = lambda/20 in FR-4",+    "nets": "[net] - the tracks to fence (e.g. [ANT_RF])",+    "offsetMm": "track centreline to via centre (default half the widest track + clearance + via radius + 0.1)",+    "pitchMm": "pitch, default 2 (a cap when freqGHz is given)",+    "size": "via diameter mm, default 0.6",+    "viaNet": "the net the vias tie to (usually GND), required"+   },+   "output": {+    "applied": "int",+    "failed": "int",+    "planned": "{vias, candidates, pitchMm, offsetMm, points}"+   },+   "hint": "Then altium_repour so the pours join the vias, and altium_run_drc. altium_return_path shows where a net still runs over a gap.",+   "pitfalls": [+    "Vias go through (top to bottom); a pour on viaNet should exist on the layers they join.",+    "Spots too close to other copper, pads or the edge are skipped, not forced."+   ],+   "related": [+    "return_path",+    "board_features",+    "repour",+    "run_drc",+    "apply_pours"+   ],+   "example": {+    "edge": true,+    "freqGHz": 2.4,+    "nets": [+     "ANT_RF"+    ],+    "viaNet": "GND"+   },+   "longRunning": false,+   "timeoutSeconds": 180+  },+  {+   "name": "activate_document",+   "tier": "app",+   "summary": "Make a document current - the quiet lane (one in-process agent call, no cursor, no OS foreground). Board and schematic read verbs act on the CURRENT document.",+   "input": {+    "path": "full path of the document (aliases: document, name)",+    "timeout": "seconds; the wait is capped at 45"+   },+   "output": {+    "data": "{path, objectKind, kind, shown}",+    "lane": "agent",+    "status": "'in_progress' when still opening",+    "windowEtiquette": "background"+   },+   "hint": "Activate the .PcbDoc, then altium_read_board; activate the .SchDoc, then altium_read_schematic. After a demo or a show_* call, activate the user's own document again.",+   "pitfalls": [+    "Needs the FULL path with a supported extension; a bare document name is refused.",+    "It is the same agent 'open': a path that is not open yet gets opened."+   ],+   "related": [+    "open_document",+    "state",+    "read_board",+    "read_schematic"+   ],+   "example": {+    "path": "C:/proj/board.PcbDoc"+   },+   "longRunning": false,+   "timeoutSeconds": 60+  },+  {+   "name": "agent_recover",+   "tier": "app",+   "summary": "Un-halt the script engine: clear the PCB transaction, press OK on open Altium dialogs, send EditScript:Stop, reap the busy notice and delete stale request files.",+   "input": {+    "reason": "string - optional, recorded in steps"+   },+   "output": {+    "steps": "[{reason} | {dialog, pressed} | {editScriptStop, exited, sec} | busy-notice reap]"+   },+   "hint": "Use after agent_timeout / no_heartbeat / instance_busy, or when Altium sits on AdomAgent_*.pas in its debugger. Then call your verb again: it restarts the agent. Never ask the user to click.",+   "pitfalls": [+    "A modal orphan (main window disabled, no dialog on screen) answers nothing: recover refuses with altium_modal_orphan instead of firing -R hand-offs that all bounce off 'another instance is busy'. Escape it with altium_close {discardChanges:true}.",+    "It presses OK on EVERY Altium dialog it finds, not only the script Error box: altium_check_dialogs first if a save prompt may be up.",+    "An agent_script_error reply was ALREADY recovered by the bridge (autoRecovered): do not recover again, and do not resend the same command - it will halt again."+   ],+   "related": [+    "check_dialogs",+    "agent_start",+    "dialog_log",+    "selftest_script_error"+   ],+   "example": {+    "reason": "agent_timeout on read_board"+   },+   "longRunning": false,+   "timeoutSeconds": 60+  },+  {+   "name": "agent_start",+   "tier": "app",+   "summary": "Bootstrap the resident DelphiScript agent inside Altium (renders the script, runs it through the -R lane, waits for the heartbeat). Optional: every agent-backed verb does this on demand.",+   "input": {+    "waitSec": "int, default 45 - how long to wait for the first heartbeat"+   },+   "output": {+    "alreadyRunning": "bool",+    "generation": "string - AdomAgent_<digest>",+    "install": "object - the rendered script",+    "startedInSec": "float",+    "strayHelpersKilled": "[pid]"+   },+   "hint": "After success altium_eval / altium_run_command / altium_state go through the agent in milliseconds. Do NOT use altium_run_script while it is up.",+   "pitfalls": [+    "The bootstrap ACTIVATES Altium for a moment (it opens the .PrjScr in the script editor); the bridge hands the user's foreground window back.",+    "no_heartbeat = the engine halted behind a script Error modal: altium_check_dialogs, then altium_agent_recover.",+    "The loop exits by itself 15 s after the last request (while it runs Altium ignores the user's clicks, #27); the next verb restarts it in 2.5-4 s. Call altium_agent_stop when you hand Altium back to the user."+   ],+   "related": [+    "agent_status",+    "agent_stop",+    "agent_recover",+    "eval"+   ],+   "example": {},+   "longRunning": false,+   "timeoutSeconds": 120+  },+  {+   "name": "agent_status",+   "tier": "app",+   "summary": "Is the agent loop alive and responsive, in which workspace, and is any loop resident anywhere? Read-only; never starts the agent.",+   "input": {},+   "output": {+    "alive": "bool - selected instance",+    "heartbeat": "{ageSec, tick, ...} | null",+    "liveWorkspaces": "[{workspace, heartbeatAgeSec}]",+    "residentAnywhere": "bool",+    "responsive": "bool",+    "workspace": "path"+   },+   "hint": "alive with responsive:false = the loop is busy inside a long Altium process: wait, do not start a second agent. Not alive: call the verb you need, it bootstraps.",+   "pitfalls": [+    "residentAnywhere:true with alive:false is a freeze-on-close hazard: altium_close tears every loop down before the app exits."+   ],+   "related": [+    "agent_start",+    "agent_recover",+    "readiness"+   ],+   "example": {},+   "longRunning": false,+   "timeoutSeconds": 10+  },+  {+   "name": "agent_stop",+   "tier": "app",+   "summary": "Stop the selected instance's resident agent loop (drops a stop file and waits up to 12 s for the heartbeat to go stale).",+   "input": {},+   "output": {+    "stoppedInSec": "float",+    "wasRunning": "bool"+   },+   "hint": "Call it when you hand Altium back to the user: while the loop is resident Altium ignores their clicks (it also stops by itself 15 s after the last verb). Needed before altium_run_script (the -R lanes refuse while a loop is resident). altium_close stops every workspace's loop itself. The next agent-backed verb restarts the agent. Clears the 'Adom is driving' taskbar badge.",+   "pitfalls": [+    "'the loop did not exit' -> altium_agent_recover.",+    "With several Altiums running pass instance: to stop the right loop."+   ],+   "related": [+    "agent_start",+    "agent_status",+    "run_script",+    "close"+   ],+   "example": {},+   "longRunning": false,+   "timeoutSeconds": 20+  },+  {+   "name": "animate_view",+   "tier": "app",+   "summary": "Camera/zoom choreography on the current document - native Altium camera commands, animated by Altium itself.",+   "input": {+    "lane": "pcb (default) | sch",+    "pauseMs": "int default per-step pause",+    "preset": "punch_in|showcase3d|orbit|flip|finale",+    "steps": "[string | {step, pauseMs}]"+   },+   "output": {+    "elapsedSec": "float",+    "stepsRun": "int"+   },+   "hint": "Runs IN-BRIDGE (~40ms per step to the agent) so timing is tight enough to narrate over. Steps: zoom_all/in/out, view_2d/3d, isometric, top/bottom/front/back/left/right, rotate90, ortho, orbit_left/right/up/down, rolls, pans, flip.",+   "pitfalls": [+    "Driving camera steps one-by-one from a container adds ~1.4s of jitter per step - that is what this verb exists to avoid.",+    "sch lane supports zoom steps only; everything else needs a PCB document current."+   ],+   "related": [+    "demo",+    "foreground",+    "run_command"+   ],+   "example": {+    "preset": "showcase3d"+   },+   "longRunning": false,+   "timeoutSeconds": 120+  },+  {+   "name": "check_dialogs",+   "tier": "app",+   "summary": "List every Altium dialog currently open, classified, each with a screenshot (the only way to READ one: Altium paints its dialog text).",+   "input": {+    "screenshots": "bool, default true"+   },+   "output": {+    "dialogs": "[{hwnd, pid, title, className, body, buttons, category, safeAction, meaning, ownedByMainInstance, rect, shot: base64 png}]",+    "dialogsDetected": "int"+   },+   "hint": "READ dialogs[].shot, decide, then altium_dismiss_dialogs {button, titles:[..]}. A script 'Error' box: altium_agent_recover. A save prompt on close: see altium_close discardChanges.",+   "pitfalls": [+    "category and safeAction are suggestions. A save_prompt is never auto-answered: it names documents that may belong to the user or another AI thread.",+    "Known nuisance boxes are pressed by the standing reaper before you ever see them; altium_dialog_log lists those."+   ],+   "related": [+    "dismiss_dialogs",+    "dialog_log",+    "agent_recover",+    "window_info"+   ],+   "example": {},+   "longRunning": false,+   "timeoutSeconds": 30+  },+  {+   "name": "close",+   "tier": "app",+   "summary": "Polite close (releases the licence seat). Frees a script engine parked on a breakpoint first (Altium refuses to close over one). discardChanges:true answers Altium's save prompts with Save None for callers whose modified documents are ALL their own; force needs acceptSeatLoss.",+   "input": {+    "acceptSeatLoss": "bool",+    "discardChanges": "bool, default false: press Save None + confirm on 'Confirm Save Locally'",+    "force": "bool",+    "instance": "pid, when several Altiums run",+    "waitSec": "float, default 20"+   },+   "output": {+    "closedInSec": "float",+    "engineFreed": "object|null",+    "errorCode": "unsaved_changes | close_blocked_by_dialog | altium_modal_orphan | modal_orphan_survived_quit | close_timeout",+    "stuckModalOrphan": "{pid,tid,hwnd} when Altium was wedged in a modal loop and the bridge ended it gracefully"+   },+   "hint": "A save prompt on close NAMES the documents - read it; on a shared machine one may belong to another AI thread. Never defaulted.",+   "pitfalls": [+    "force:true without acceptSeatLoss is refused: a kill strands the On-Demand seat.",+    "errorCode altium_modal_orphan = Altium is stuck inside a modal loop whose dialog is GONE (its main window is disabled, nothing on screen to answer). No menu, keystroke or -R reaches it and it still holds the seat. With discardChanges:true the bridge ends it by posting WM_QUIT - Altium's own shutdown, which RELEASES the seat. Never reach for force/taskkill first."+   ],+   "related": [+    "license",+    "launch"+   ],+   "example": {},+   "longRunning": false,+   "timeoutSeconds": 120+  },+  {+   "name": "close_document",+   "tier": "app",+   "summary": "Close ONE open document by path through its document handle. Never saves.",+   "input": {+    "path": "absolute path of the open document (required)"+   },+   "output": {+    "data": "{closed: bool, path} | {closed: false, note: 'not open: <path>'}",+    "roundtripMs": "int"+   },+   "hint": "altium_save {filePath} first if anything is worth keeping. After re-running altium_place_on_sch / altium_test_board to the same path, close the stale document before altium_show_*.",+   "pitfalls": [+    "Unsaved edits are DISCARDED without a prompt.",+    "success:true with closed:false means the document was not open."+   ],+   "related": [+    "save",+    "open_document",+    "state"+   ],+   "example": {+    "path": "C:/proj/tof.SchDoc"+   },+   "longRunning": false,+   "timeoutSeconds": 30+  },+  {+   "name": "connect_workspace",+   "tier": "app",+   "summary": "Open Altium's workspace picker (the 'Not Connected' menu) and return a screenshot with a coordMap. The list is a custom control with no UIA children, so the caller clicks the row.",+   "input": {},+   "output": {+    "picker": "{hwnd, title, className: TWorkspacesForm, rect}",+    "shot": "ab desktop_screenshot_window reply: image + coordMap/shotId",+    "windowEtiquette": "background"+   },+   "hint": "Click the wanted row with ab's desktop_click {space:'image', shotId, x, y} straight away, then altium_signin_status to confirm.",+   "pitfalls": [+    "picker_not_open usually means Altium is ALREADY connected (the menu shows the workspace name).",+    "The picker is a popup: it closes on focus loss, so click before doing anything else.",+    "A workspace with no free Author seat connects view-only: new documents then fail with view_only_no_author_seat."+   ],+   "related": [+    "signin_status",+    "a365_status",+    "license"+   ],+   "example": {},+   "longRunning": false,+   "timeoutSeconds": 60+  },+  {+   "name": "demo",+   "tier": "app",+   "summary": "The tour. Default: one background beat per call with a screenshot. live:true = the narrated FOREGROUND show: ab caption + document + in-bridge camera choreography per stop, returning the narration text for YOUR TTS.",+   "input": {+    "live": "bool",+    "stage": "int 0-5"+   },+   "output": {+    "done": "bool",+    "narrate": "string - speak this",+    "nextCall": "{verb, args}",+    "nextNarrate": "string"+   },+   "hint": "live SYNC MODEL: pre-cache narration with `adom-tts load` (one marker per stop), then per stop fire `adom-tts cue <id> <n> --confirm` in the background and IMMEDIATELY make nextCall - the prefetched cue starts instantly and the ~5-17s of on-screen motion runs under the voice; advance when BOTH finish. If Altium is cold, the first call answers status:launching with a narrate line - speak it, poll again in ~20s.",+   "pitfalls": [+    "The caller owns the VOICE (adom-tts) - the bridge never synthesizes speech.",+    "After done:true, restore the user's own document (altium_activate_document)."+   ],+   "related": [+    "animate_view",+    "foreground",+    "show_symbol"+   ],+   "example": {+    "live": true,+    "stage": 0+   },+   "longRunning": false,+   "timeoutSeconds": 180+  },+  {+   "name": "describe",+   "tier": "app",+   "summary": "This list - the bridge's verbs and schemas for ab's Verbs tab.",+   "input": {},+   "output": {+    "verbs": "[{name, summary, input, output, ...}]"+   },+   "hint": "Static; safe to call any time.",+   "pitfalls": [],+   "related": [+    "status"+   ],+   "example": {},+   "longRunning": false,+   "timeoutSeconds": 5+  },+  {+   "name": "diagnostics",+   "tier": "app",+   "summary": "Bridge-side facts for a bug report: environment variables, every Altium install found, Altium's windows, the agent status block and the cache directory.",+   "input": {},+   "output": {+    "agent": "the altium_agent_status reply",+    "cacheDir": "path",+    "env": "{LOCALAPPDATA, APPDATA, USERPROFILE, ProgramFiles, ADOM_BIND_HOST, ADOM_DIRECT_API_URL}",+    "installs": "[{base_dir, exe, system_dir, major}] newest major first",+    "windows": "[{hwnd, title, kind, className, pid, rect}]"+   },+   "hint": "installs:[] means Altium is not installed (altium_install). Otherwise altium_readiness for the verdict and altium_instances when several Altiums run.",+   "pitfalls": [+    "Instance-exempt: it runs with no instance selected, so agent.alive reads the legacy workspace; agent.liveWorkspaces is the union that counts."+   ],+   "related": [+    "readiness",+    "agent_status",+    "instances"+   ],+   "example": {},+   "longRunning": false,+   "timeoutSeconds": 15+  },+  {+   "name": "dialog_log",+   "tier": "app",+   "summary": "What the standing dialog reaper (and the bridge's own recoveries) pressed, newest last: nuisance boxes, script-error boxes, discarded save prompts, silent engine halts cleared.",+   "input": {+    "limit": "int, default 25"+   },+   "output": {+    "reaped": "[{title, category, pressed, ok, note, at}]",+    "running": "bool"+   },+   "hint": "Read it after a surprise: enginePreviouslyParked on a reply, an agent_script_error, or an open that failed view-only ('Workspace Unavailable' shows here). A dialog that reached the user and is NOT listed is a catalog gap: report it.",+   "pitfalls": [+    "An in-memory ring of the last 200 events: a bridge restart clears it.",+    "Save prompts and unknown dialogs never appear here as pressed: the reaper does not touch them."+   ],+   "related": [+    "check_dialogs",+    "status",+    "agent_recover"+   ],+   "example": {+    "limit": 10+   },+   "longRunning": false,+   "timeoutSeconds": 10+  },+  {+   "name": "dismiss_dialogs",+   "tier": "app",+   "summary": "Press one named button (default OK) on the detected Altium dialogs through UIA - no cursor, no focus steal. titles narrows it to dialogs whose title contains one of the strings.",+   "input": {+    "button": "string, default 'OK' - the button's accessible name",+    "titles": "[string] - optional title substrings; only matching dialogs are pressed"+   },+   "output": {+    "detected": "int",+    "pressed": "[{title, hwnd, button, ok}]"+   },+   "hint": "ALWAYS pass titles. Then altium_check_dialogs to confirm nothing is left, and repeat the verb that was blocked.",+   "pitfalls": [+    "Without titles the button is pressed on EVERY detected Altium dialog, save prompts included.",+    "hwnd is NOT read by this verb, although other verbs' hints say {hwnd, button}: target a dialog with titles.",+    "success is true even when nothing was pressed or the click failed: check pressed[].ok."+   ],+   "related": [+    "check_dialogs",+    "close",+    "agent_recover"+   ],+   "example": {+    "button": "OK",+    "titles": [+     "Error"+    ]+   },+   "longRunning": false,+   "timeoutSeconds": 30+  },+  {+   "name": "ensure_licensed",+   "tier": "app",+   "summary": "One call to get a usable Altium: launch if needed, then claim a seat (Use License via UIA). Optional escalations: sign out to force a seat release, or start the free-trial flow.",+   "input": {+    "allowSignInReset": "bool - if no seat is free, Sign Out (the human must sign back in)",+    "allowTrial": "bool - if no seat is free, open the trial flow in the browser",+    "launchWaitSec": "int, default 240"+   },+   "output": {+    "alreadyLicensed": "bool",+    "licenceState": "string",+    "needsHuman": "string - only for browser sign-in / trial activation / a licence the account lacks",+    "page": "{notUsingLicence, requestAccessBanner, viewerOnly} read off the licence page",+    "seatStuck": "bool",+    "shot": "{..}",+    "steps": "[..]",+    "summary": "string"+   },+   "hint": "success -> drive. errorCode no_seat_available: find the Altium holding the seat (altium_instances here, or another machine) and altium_close it, then call again. errorCode no_licence_on_account: the account has only the Viewer; ask the user (admin assigns a licence, trial, or the other Altium build). errorCode no_author_seat (Develop): every workspace Author seat is taken; ask the user.",+   "pitfalls": [+    "One On-Demand seat serves EVERY Altium on the account. A KILLED Altium strands the seat (page shows Seats 0/1 while every claim fails): altium_close releases it, waiting does not.",+    "allowSignInReset:true signs the user OUT and ends with needsHuman (browser sign-in). Use it only when nothing else holds the seat.",+    "It may launch Altium, which raises Altium over the user's work once."+   ],+   "related": [+    "license",+    "license_status",+    "instances",+    "close"+   ],+   "example": {},+   "longRunning": false,+   "timeoutSeconds": 300+  },+  {+   "name": "eval",+   "tier": "app",+   "summary": "Run a DelphiScript snippet inside Altium (optional Var block, then Begin ... End;) on the CURRENT document, or on filePath opened first.",+   "input": {+    "filePath": "optional: the document to make current first (a .PcbDoc for PCBServer.GetCurrentPCBBoard)",+    "text": "string - [Var X : Integer;] Begin ... End;",+    "timeout": "int seconds"+   },+   "output": {+    "errorCode": "script_error",+    "errorShot": "PNG of Altium's error box",+    "success": "bool (false + errorShot when Altium raised an error box)"+   },+   "hint": "Single quotes only (no double quotes). A Var block before Begin is allowed. Capture output by writing a file, not by returning a value. Pass filePath: after an export the OutJob is current and GetCurrentPCBBoard is Nil.",+   "pitfalls": [+    "An undeclared identifier HALTS the engine with a modal that Try/Except cannot catch.",+    "ShowMessage blocks the loop behind a modal - never use it."+   ],+   "related": [+    "run_command"+   ],+   "example": {+    "text": "Begin PCBServer.GetCurrentPCBBoard.ViewManager_FullUpdate; End;"+   },+   "longRunning": false,+   "timeoutSeconds": 30+  },+  {+   "name": "extension_ping",+   "tier": "app",+   "summary": "Does the AdomBridge .NET extension answer? Sends AdomBridge:Ping through the -R lane and waits up to 30 s for its result file.",+   "input": {},+   "output": {+    "elapsedSec": "float",+    "installedOnDisk": "bool",+    "result": "string"+   },+   "hint": "success -> altium_export_step / altium_import_kicad will work. installedOnDisk:true with extension_timeout = installed but not loading on this Altium build.",+   "pitfalls": [+    "It spawns an X2 -R helper against the running instance: do not loop it."+   ],+   "related": [+    "install_extension",+    "export_step",+    "import_kicad"+   ],+   "example": {},+   "longRunning": false,+   "timeoutSeconds": 60+  },+  {+   "name": "foreground",+   "tier": "app",+   "summary": "Bring Altium in front of the user - the sanctioned exception to the never-foreground rule.",+   "input": {+    "reason": "string >=10 chars, written for the HUMAN (required)"+   },+   "output": {+    "windowEtiquette": "foreground"+   },+   "hint": "For a human who asked to WATCH (a live demo, a walkthrough). Everything else stays background.",+   "pitfalls": [+    "Refused without a real reason - same rule as ab's desktop_caption."+   ],+   "related": [+    "demo",+    "animate_view"+   ],+   "example": {+    "reason": "John asked to watch the import live"+   },+   "longRunning": false,+   "timeoutSeconds": 15+  },+  {+   "name": "install",+   "tier": "app",+   "summary": "Install Altium Designer on this machine, STAGED, with the rules in the verb (from 0.2.29). stage: preflight | get_installer | launch | wait | license, or omit it and 'auto' picks the stage from the machine's state. The verb looks before it asks, verifies the installer's Authenticode signature (Valid, CN=Altium LLC) before anything runs, refuses to launch into an empty room, toasts the human itself and says truthfully whether the toast displayed, and does the licensing clicks (Refresh / Use License) ITSELF.",+   "input": {+    "humanIsPresent": "bool, REQUIRED true for stage launch: the UAC prompt auto-denies in ~2 minutes",+    "installerPath": "optional path; default = newest AltiumDesignerSetup*.exe in Downloads",+    "stage": "auto (default) | preflight | get_installer | launch | wait | license"+   },+   "output": {+    "_next": "[string]",+    "licensed": "bool",+    "needsHuman": "[string] - only ever a password, the UAC prompt, or clicks in an elevated window",+    "stage": "string",+    "toast": "{displayed, _hint}"+   },+   "hint": "Call it with no args and follow _next. The human's part ENDS at the installer's Finish button; licensing is yours.",+   "pitfalls": [+    "needsHuman may only name a password/2FA, the UAC prompt, or clicks inside an ELEVATED window. Release, Assign, Use License, Refresh and save prompts are ordinary clicks: do them.",+    "LOOK before you ask: 'Sign Out' on the licence page means the user is ALREADY signed in (the installer's browser sign-in carries over).",+    "'There are no seats available' means another running Altium holds the account's single On-Demand seat: altium_close it there, then stage license again.",+    "Never use the installer's unattended switches: they take the account password on the command line.",+    "A toast only reaches the machine it is shown on. Check toast.displayed and ALSO tell the user in chat."+   ],+   "related": [+    "ensure_licensed",+    "license",+    "launch",+    "readiness"+   ],+   "example": {+    "humanIsPresent": true,+    "stage": "launch"+   },+   "longRunning": false,+   "timeoutSeconds": 120+  },+  {+   "name": "install_extension",+   "tier": "app",+   "summary": "Install, update or remove the AdomBridge .NET extension (the PowerShell installer shipped beside the bridge). Altium must be CLOSED.",+   "input": {+    "remove": "bool, default false - uninstall instead"+   },+   "output": {+    "output": "string - installer stdout tail",+    "removed": "bool",+    "stderr": "string"+   },+   "hint": "Sequence: altium_close, altium_install_extension, altium_launch, altium_extension_ping. Only altium_export_step and altium_import_kicad need it.",+   "pitfalls": [+    "Refused with altium_running while any Altium is up (it holds AdomBridge.dll open); remove:true is allowed while running.",+    "A failed extension load can stop Altium starting: put extension/REMOVE-AdomBridge.cmd on the Desktop first.",+    "It was unregistered on AD 26.9.1 after coinciding with a 'Could not start Vault Explorer' warning: install only when you need those two verbs."+   ],+   "related": [+    "extension_ping",+    "export_step",+    "import_kicad",+    "close"+   ],+   "example": {},+   "longRunning": false,+   "timeoutSeconds": 200+  },+  {+   "name": "install_receipt",+   "tier": "app",+   "summary": "The last bundle install's full receipt (proof frame included), for a client whose install call outlived ab's budget.",+   "input": {},+   "output": {+    "receipt": "object|null - the install_library_bundle reply"+   },+   "hint": "Read after altium_status shows lastInstall with your installId.",+   "pitfalls": [],+   "related": [+    "install_library_bundle",+    "status"+   ],+   "example": {},+   "longRunning": false,+   "timeoutSeconds": 10+  },+  {+   "name": "instances",+   "tier": "app",+   "summary": "Every running Altium (Designer + Designer Develop can run together).",+   "input": {},+   "output": {+    "count": "int",+    "instances": "[{pid, key, edition, licensed, agentAlive, ...}]"+   },+   "hint": "With several running, other verbs REFUSE without instance:<pid|key|word> - guessing means acting inside another AI thread's Altium.",+   "pitfalls": [+    "Develop writes NO licence tail when it HOLDS the seat - blank licenceState there means licensed."+   ],+   "related": [+    "status"+   ],+   "example": {},+   "longRunning": false,+   "timeoutSeconds": 15+  },+  {+   "name": "launch",+   "tier": "app",+   "summary": "Start Altium (the newest numbered install, or the one install names) and wait for its main window, then hand the user's own window back. Answers alreadyRunning when one is up.",+   "input": {+    "background": "bool, default TRUE: remember the user's foreground window and restore it once Altium is up. Pass false only when the user asked to SEE Altium come up.",+    "install": "optional: develop | designer | a folder name (AD26, ADDevelop) | a path, when several Altiums are installed",+    "waitSec": "number, default 180"+   },+   "output": {+    "alreadyRunning": "bool",+    "foregroundReturnedTo": "hwnd|null",+    "licence": "{running, licensed, state, adVersion, edition, hwnd, pid}",+    "startedInSec": "float",+    "windowEtiquette": "background once the user's window is handed back; foreground only with background:false"+   },+   "hint": "Cold start is about 60 s. Next: altium_readiness; licensed:false -> altium_ensure_licensed. Most verbs need this first (errorCode altium_not_running).",+   "pitfalls": [+    "Altium foregrounds ITSELF at startup and offers no switch for it, so the window jumps over the user's work for a few seconds even with background:true \u2014 the bridge can only hand the focus back afterwards. Call it once; never in a retry loop.",+    "errorCode host_app_not_installed -> altium_install. launch_timeout = no main window after waitSec.",+    "A 'Workspace Unavailable' box at launch (altium_dialog_log) means another running Altium holds the seat: this one is view-only and opens fail with view_only_no_author_seat."+   ],+   "related": [+    "readiness",+    "ensure_licensed",+    "close",+    "install"+   ],+   "example": {},+   "longRunning": false,+   "timeoutSeconds": 240+  },+  {+   "name": "license",+   "tier": "app",+   "summary": "Drive the License Management page (UIA first - no cursor, no foreground).",+   "input": {+    "action": "show|refresh|use_license|start_trial|sign_out"+   },+   "output": {+    "shot": "{image: base64 png, width, height}",+    "success": "bool",+    "windowEtiquette": "background"+   },+   "hint": "One On-Demand seat serves EVERY Altium app on this machine. use_license claims a free seat.",+   "pitfalls": [+    "NEVER kill an Altium holding the seat: the lease strands, the page shows Seats 0/1 while every claim fails. altium_close releases it."+   ],+   "related": [+    "ensure_licensed",+    "license_status",+    "close"+   ],+   "example": {+    "action": "use_license"+   },+   "longRunning": false,+   "timeoutSeconds": 60+  },+  {+   "name": "license_status",+   "tier": "app",+   "summary": "Plain-language answer to 'what Altium licence am I on?': the state from the window title plus a capture of the License Management page.",+   "input": {},+   "output": {+    "licenceState": "string",+    "licensed": "bool | null",+    "running": "bool",+    "seatStuck": "'possible' | null",+    "shot": "{image: base64 png, ...}",+    "summary": "string"+   },+   "hint": "Read shot.image for the product row (Trial/Full, days left, seat n/n): that lives ONLY on the page. Not licensed -> altium_ensure_licensed.",+   "pitfalls": [+    "It brings up the License Management page inside Altium (when the agent is resident), replacing the view the user had.",+    "One On-Demand seat serves every Altium on the account: 'no seats available' usually means ANOTHER running Altium holds it - altium_close that one."+   ],+   "related": [+    "ensure_licensed",+    "license",+    "instances"+   ],+   "example": {},+   "longRunning": false,+   "timeoutSeconds": 60+  },+  {+   "name": "list_commands",+   "tier": "app",+   "summary": "Search Altium's process-launcher catalog (about 1,100 Server:Command entries parsed from the install's System/*.rcs files) by text and/or server.",+   "input": {+    "limit": "int, default 50",+    "query": "string - substring over command, captions, descriptions and params",+    "server": "string - e.g. PCB, Sch, WorkspaceManager"+   },+   "output": {+    "count": "int",+    "hits": "[{command, server, captions, shortcuts, params, descriptions}]",+    "servers": "{server: count}",+    "total": "int - distinct commands"+   },+   "hint": "Run a hit with altium_run_command {command, params}; hits[].params shows the Name=Value pairs Altium's own menus pass.",+   "pitfalls": [+    "Reads files, not Altium: works with Altium closed but answers host_app_not_installed without an install.",+    "Being listed does not make a process safe: some open a modal that blocks the agent (PCB:DesignRuleCheck - use altium_run_drc)."+   ],+   "related": [+    "run_command",+    "eval"+   ],+   "example": {+    "query": "zoom",+    "server": "PCB"+   },+   "longRunning": false,+   "timeoutSeconds": 30+  },+  {+   "name": "open_document",+   "tier": "app",+   "summary": "Open a document or project by path through the agent and show it without focusing it. An already-open document is activated, not reloaded.",+   "input": {+    "filePath": "absolute Windows path (alias: path)",+    "timeout": "seconds; the wait is capped at 45"+   },+   "output": {+    "data": "{path, objectKind, kind, shown: bool, lane}",+    "roundtripMs": "int",+    "status": "'in_progress' when Altium is still opening it",+    "windowEtiquette": "background"+   },+   "hint": "Then the verb for that document: altium_read_board / altium_read_schematic / altium_list_library. status:in_progress -> poll altium_status or altium_window_info until it is current.",+   "pitfalls": [+    "Only .PcbDoc .SchDoc .PcbLib .SchLib .OutJob .Pcb3DLib .Prj* .LibPkg .txt .pas .rep are accepted; anything else is refused as unsupported_document (Altium would raise a 'File type not recognised' modal).",+    "success:true with data.shown:false on a non-project means the document did NOT open (this verb does not turn that into an error). In a view-only Altium that is every new document: see view_only_no_author_seat."+   ],+   "related": [+    "activate_document",+    "close_document",+    "state",+    "show_2d_board"+   ],+   "example": {+    "filePath": "C:/proj/board.PcbDoc"+   },+   "longRunning": false,+   "timeoutSeconds": 60+  },+  {+   "name": "open_project",+   "tier": "app",+   "summary": "Open a project (.PrjPcb / .PrjScr / .PrjMbd / .PrjHar / .LibPkg) by path. Same handler as altium_open_document.",+   "input": {+    "filePath": "absolute Windows path (alias: path)",+    "timeout": "seconds; the wait is capped at 45"+   },+   "output": {+    "data": "{path, objectKind: 'Project', shown: false, note}",+    "roundtripMs": "int",+    "windowEtiquette": "background"+   },+   "hint": "Then altium_compile {projectPath} for ERC, or altium_open_document one of its sheets. altium_state lists the project's documents.",+   "pitfalls": [+    "data.shown:false is NORMAL for a project: projects have no document view."+   ],+   "related": [+    "open_document",+    "compile",+    "show_project",+    "state"+   ],+   "example": {+    "filePath": "C:/proj/board.PrjPcb"+   },+   "longRunning": false,+   "timeoutSeconds": 60+  },+  {+   "name": "panel",+   "tier": "app",+   "summary": "Activate a docked panel TAB (SCH Library, PCB Library, Components, Projects, Properties) in the BACKGROUND - the 'ganged-up view' needs the right panels in front, and no Altium server process switches a tab.",+   "input": {+    "ensureId": "string optional - panel ID for Client:CustomizeResources (e.g. TSchLibPanel) when the panel may not exist yet",+    "name": "string - the tab caption exactly as painted, e.g. 'SCH Library'"+   },+   "output": {+    "elapsedSec": "float",+    "lane": "already | ui_select | ui_select_raised",+    "selected": "bool",+    "verified": "bool (false when the tab exposes no selection state)"+   },+   "hint": "One UIA search reads the tab's existence AND selection, then at most one SelectionItemPattern select and one verify, all inside a 24 s budget: every exit is a named answer (panel_not_found, panel_search_timeout, ui_select_gated, panel_not_selected), never ab's generic timeout.",+   "pitfalls": [+    "ab gates desktop_ui_select behind the shell approval grant; without it the verb reports ui_select_gated instead of pretending.",+    "A plain click does not switch panel tabs - that is why this verb exists.",+    "On Altium Designer Develop the UI Automation search does not finish in time (panel_search_timeout, measured 2026-10-15); opening a library document already brings its library panel to the front.",+    "lane ui_select_raised means ab fell back to a synthetic click that foregrounded Altium; the dispatch wrapper hands the user's window back."+   ],+   "related": [+    "show_symbol",+    "show_footprint",+    "run_command"+   ],+   "example": {+    "name": "SCH Library"+   },+   "longRunning": false,+   "timeoutSeconds": 30+  },+  {+   "name": "progress",+   "tier": "app",+   "summary": "The bridge's timing book: phases in flight right now (percent/ETA blocks ab paints on the taskbar), measured phase history and per-verb run statistics.",+   "input": {},+   "output": {+    "active": "[progress blocks of phases in flight]",+    "history": "{phase: {warm|cold: {samples, p50, p90}}}",+    "verbTimes": "{verb: {count, p50, p90, max, failures}} - p90 descending, top 40"+   },+   "hint": "Use verbTimes.p90 to choose a timeout for a slow verb on THIS machine, and active[] to see what a long call is doing. Never touches Altium.",+   "pitfalls": [],+   "related": [+    "status",+    "describe"+   ],+   "example": {},+   "longRunning": false,+   "timeoutSeconds": 10+  },+  {+   "name": "readiness",+   "tier": "app",+   "summary": "The one-call gate: is Altium installed, running, licensed, and is the resident agent alive? Answers with Altium closed and never touches the script engine.",+   "input": {},+   "output": {+    "adVersion": "string | null",+    "agentAlive": "bool - the selected instance",+    "agentLiveWorkspaces": "[{workspace, heartbeatAgeSec}] - only when a loop lives outside the selection",+    "agentResidentAnywhere": "bool - any workspace",+    "install": "{base_dir, exe, system_dir, major} | null",+    "installed": "bool",+    "licenceState": "string",+    "licensed": "bool | null",+    "ready": "bool - all four true",+    "running": "bool"+   },+   "hint": "Follow _hint: not installed -> altium_install; not running -> altium_launch; licensed:false -> altium_ensure_licensed; agent down -> just call the verb you need (it bootstraps the agent) or altium_agent_start. ready:true -> altium_state.",+   "pitfalls": [+    "agentAlive is the SELECTED instance's view. agentResidentAnywhere:true beside agentAlive:false means a loop lives in another workspace: close through altium_close, never the window X."+   ],+   "related": [+    "status",+    "launch",+    "ensure_licensed",+    "agent_start"+   ],+   "example": {},+   "longRunning": false,+   "timeoutSeconds": 15+  },+  {+   "name": "run_command",+   "tier": "app",+   "summary": "Run an Altium process (Server:Command) through the agent, on the CURRENT document or on filePath opened first.",+   "input": {+    "command": "string - e.g. PCB:Zoom",+    "filePath": "optional: the document to make current first (PCB: processes need a board current)",+    "params": "string or object"+   },+   "output": {+    "success": "bool"+   },+   "hint": "Params are pipe-joined key=value pairs. altium_list_commands searches the catalog.",+   "pitfalls": [+    "Some processes open a modal dialog and block the loop until it is answered."+   ],+   "related": [+    "eval",+    "list_commands"+   ],+   "example": {+    "command": "PCB:Zoom",+    "params": {+     "Action": "All"+    }+   },+   "longRunning": false,+   "timeoutSeconds": 60+  },+  {+   "name": "run_script",+   "tier": "app",+   "summary": "One shot: run a procedure of YOUR OWN .PrjScr through Altium's -R command line. Only for when the agent is NOT resident.",+   "input": {+    "prjscr": "absolute path to a .PrjScr on this machine (required)",+    "proc": "string, default 'Main'",+    "timeout": "seconds, default 60"+   },+   "output": {+    "arg": "string - the -R argument sent",+    "helperSec": "float",+    "modalsPreempted": "[{title, body}] - bridge-caused boxes cleared in flight"+   },+   "hint": "While the agent is resident use altium_eval / altium_run_command instead (or altium_agent_stop first). Scripts return nothing: have yours write a file and read that.",+   "pitfalls": [+    "Refused with agent_resident while ANY agent loop is alive: a second script raises 'Another script executing now' on the user's screen.",+    "ONE SHOT: instance_busy means the running instance did not accept the request. Do not retry in a loop; each attempt costs the user a busy notice."+   ],+   "related": [+    "eval",+    "run_command",+    "agent_stop",+    "agent_recover"+   ],+   "example": {+    "prjscr": "C:/scripts/MyTool.PrjScr",+    "proc": "Main"+   },+   "longRunning": false,+   "timeoutSeconds": 60+  },+  {+   "name": "save",+   "tier": "app",+   "summary": "Save the focused document, or open filePath first and save that (WorkspaceManager:SaveObject on the focused document).",+   "input": {+    "filePath": "path - optional; opened and made current before the save"+   },+   "output": {+    "data": "{process, params}",+    "roundtripMs": "int"+   },+   "hint": "Pass filePath every time: every library authoring verb leaves the library UNSAVED. Then altium_check_dialogs - a 'Save As' box means the document never had a path.",+   "pitfalls": [+    "Without filePath it saves whatever has focus, which may be the user's document rather than yours.",+    "success only means the save process was dispatched; a Save As dialog blocks the engine until answered."+   ],+   "related": [+    "close_document",+    "check_dialogs",+    "close"+   ],+   "example": {+    "filePath": "C:/lib/Adom_ToF.SchLib"+   },+   "longRunning": false,+   "timeoutSeconds": 60+  },+  {+   "name": "sch_zoom",+   "tier": "app",+   "summary": "Find parts on a sheet: select them (everything else deselected), frame the view on them and return a proof frame. The schematic half of cross-probing (the board half: altium_zoom_to).",+   "input": {+    "designators": "[string] or 'R1,U2' (required)",+    "sheet": "path to the .SchDoc (required)",+    "shot": "bool, default true"+   },+   "output": {+    "notOnSheet": "[string]",+    "parts": "[{designator, xMil, yMil}]",+    "shot": "frame of Altium's main window"+   },+   "hint": "To find which sheet holds a part, altium_bom (or altium_schematic_data per sheet) lists every designator with its sheet.",+   "pitfalls": [],+   "related": [+    "zoom_to",+    "schematic_data",+    "sch_edit"+   ],+   "example": {+    "designators": [+     "U1",+     "L1"+    ],+    "sheet": "C:/proj/Main.SchDoc"+   },+   "longRunning": false,+   "timeoutSeconds": 60+  },+  {+   "name": "select_net",+   "tier": "app",+   "summary": "Select one net as a whole on the current board (tracks, arcs, vias, pads, pours) and nothing else, optionally framing it; no net clears the selection.",+   "input": {+    "margin": "mm, default 1",+    "net": "string (optional) - empty or absent clears",+    "zoom": "bool - frame the net (altium_zoom_to)"+   },+   "output": {+    "byKind": "{tracks, arcs, vias, pads, pours, other}",+    "net": "string",+    "selected": "int",+    "zoom": "[l, b, r, t] mm when zoom"+   },+   "hint": "AI Flow's net walkthrough lights each loaded net in turn. Altium draws a selection in its highlight colour.",+   "pitfalls": [+    "Needs the board current.",+    "Pour regions are counted once, as their polygon."+   ],+   "related": [+    "zoom_to",+    "routing_state",+    "list_nets"+   ],+   "example": {+    "net": "SW",+    "zoom": true+   },+   "longRunning": false,+   "timeoutSeconds": 120+  },+  {+   "name": "selection",+   "tier": "app",+   "summary": "What the user has selected in the current board (components with position mm, rotation, side, footprint, size) or sheet (components with location mils and orientation).",+   "input": {+    "filePath": "optional: the document to make current first",+    "select": "[refs] - optional: select exactly these first (everything else deselected), to show the user which parts you mean"+   },+   "output": {+    "kind": "board | sheet",+    "selected": "[{ref, x, y, rotation, side, footprint, widthMm, heightMm}] or [{designator, xMil, yMil, orientation, libReference, comment}]"+   },+   "hint": "Lets the user point at parts in Altium and say 'these': move them with altium_move_parts {ref, dx, dy}, replicate them with altium_replicate_layout.",+   "pitfalls": [+    "Only components are reported (selected tracks and vias are not)."+   ],+   "related": [+    "move_parts",+    "replicate_layout",+    "zoom_to"+   ],+   "example": {},+   "longRunning": false,+   "timeoutSeconds": 60+  },+  {+   "name": "selftest_script_error",+   "tier": "app",+   "summary": "Regression test for the script-error safety net (from 0.2.23): makes the agent raise a REAL DelphiScript runtime error on purpose and checks that the bridge photographs Altium's Error box, stops the halted script itself and leaves no dialog behind. Flashes one error box for under a second. Run it after any change to agent.rs / dialogs.rs and once on every new Altium build.",+   "input": {},+   "output": {+    "autoRecovered": "bool",+    "dialogsLeft": "int",+    "errorShot": "base64 png (halted outcome only)",+    "netWorks": "bool",+    "outcome": "halted_and_recovered | contained_by_agent | unhandled"+   },+   "hint": "netWorks:true is the pass. The agent is stopped afterwards by design; the next verb restarts it.",+   "pitfalls": [+    "It really does halt the engine for a moment: do not run it while a user is mid-edit in Altium.",+    "By design this is the SLOWEST verb in the catalog: it halts the engine, waits for the halt to be seen, recovers it and verifies by restarting the agent. Altium Designer Develop parks SILENTLY (no Error box), so the halt is only seen when the wait expires \u2014 measured 18 s to over 60 s on the same machine. Its budget is 180 s for that reason; a timeout here means the safety net is broken, not that it was slow."+   ],+   "related": [+    "agent_recover",+    "dialog_log",+    "agent_status"+   ],+   "example": {},+   "longRunning": false,+   "timeoutSeconds": 180+  },+  {+   "name": "set_layer_visibility",+   "tier": "app",+   "summary": "Show or hide one layer of the current board (by layer id) and refresh the view and the layer tabs.",+   "input": {+    "displayed": "bool, default true",+    "layerId": "int (required) - an id from altium_layers"+   },+   "output": {+    "data": "{layerId, name, displayed}",+    "roundtripMs": "int"+   },+   "hint": "Then altium_show_2d_board {filePath} for a frame of the result. Restore what you hid: visibility is view state the user keeps seeing.",+   "pitfalls": [+    "Needs a .PcbDoc current.",+    "Only pass ids that altium_layers returned: the id is not range-checked before the layer object is read."+   ],+   "related": [+    "layers",+    "layers_in_use",+    "show_2d_board"+   ],+   "example": {+    "displayed": false,+    "layerId": 1+   },+   "longRunning": false,+   "timeoutSeconds": 60+  },+  {+   "name": "setup_trial",+   "tier": "app",+   "summary": "Onboarding state machine: from nothing to a working trial. Each call reports the stage it found (install | launch | signin | done) and the ONE next action; human steps are named, not faked.",+   "input": {},+   "output": {+    "licenceState": "string",+    "needsHuman": "string - when a person must act",+    "nextAction": "string",+    "stage": "install | launch | signin | done",+    "summary": "string"+   },+   "hint": "Follow nextAction and call again until stage is done. Not installed -> altium_install (the staged install verb; its licence stage does the clicks itself).",+   "pitfalls": [+    "Signed in but unlicensed, it starts the free-trial flow in the BROWSER and answers trial_needs_browser: a person names the workspace, accepts terms and clicks Activate.",+    "stage launch answers success:true without launching anything: call altium_launch yourself."+   ],+   "related": [+    "install",+    "ensure_licensed",+    "signin_status",+    "launch"+   ],+   "example": {},+   "longRunning": false,+   "timeoutSeconds": 300+  },+  {+   "name": "show_2d_board",+   "tier": "app",+   "summary": "Open a .PcbDoc, switch to 2D, zoom to fit, and return a proof frame. With no filePath it opens the bundled Bluetooth Sentinel board.",+   "input": {+    "filePath": "path to a .PcbDoc - default: the bundled example board",+    "timeout": "seconds, default 120"+   },+   "output": {+    "dialogs": "[..]",+    "dialogsDetected": "int",+    "document": "string",+    "elapsedSec": "float",+    "path": "string",+    "shot": "{image: base64 png, width, height}",+    "steps": "[{open | process, ...}]",+    "surface": "2d_board",+    "title": "string",+    "windowEtiquette": "background"+   },+   "hint": "It also makes the board the CURRENT document, so altium_read_board / altium_board_summary work right after. altium_show_3d_board for the 3D frame.",+   "pitfalls": [+    "ALWAYS pass filePath: the default opens Altium's example board in the user's session.",+    "Measured live 2026-09-20: it RAISES Altium over the user's work. One call per proof; never loop it."+   ],+   "related": [+    "show_3d_board",+    "read_board",+    "set_layer_visibility",+    "animate_view"+   ],+   "example": {+    "filePath": "C:/proj/tof.PcbDoc"+   },+   "longRunning": false,+   "timeoutSeconds": 120+  },+  {+   "name": "show_3d_board",+   "tier": "app",+   "summary": "Open a .PcbDoc, switch to 3D, zoom to fit, and return a proof frame. With no filePath it opens the bundled Bluetooth Sentinel board.",+   "input": {+    "filePath": "path to a .PcbDoc - default: the bundled example board",+    "timeout": "seconds, default 120"+   },+   "output": {+    "dialogs": "[..]",+    "dialogsDetected": "int",+    "document": "string",+    "elapsedSec": "float",+    "path": "string",+    "shot": "{image: base64 png, width, height}",+    "steps": "[{open | process, ...}]",+    "surface": "3d_board",+    "title": "string",+    "windowEtiquette": "background"+   },+   "hint": "The proof for altium_test_board {pcbPath}: check every part shows a body. Missing body = the footprint has none (altium_add_body). altium_animate_view {preset:'showcase3d'} for motion.",+   "pitfalls": [+    "ALWAYS pass filePath: the default opens Altium's example board in the user's session.",+    "Measured live 2026-09-20: it RAISES Altium over the user's work. One call per proof; never loop it.",+    "It leaves the board in 3D mode for the user; altium_show_2d_board switches back."+   ],+   "related": [+    "show_2d_board",+    "test_board",+    "add_body",+    "animate_view"+   ],+   "example": {+    "filePath": "C:/proj/tof.PcbDoc"+   },+   "longRunning": false,+   "timeoutSeconds": 120+  },+  {+   "name": "show_3d_chip",+   "tier": "app",+   "summary": "Open a PcbLib member in the footprint editor in 3D, zoom to fit, and capture a proof frame of the part's 3D body.",+   "input": {+    "footprintName": "string (alias: name) - default: the first footprint of YOUR library, SO-G3 in the demo library",+    "libraryPath": "path to a .PcbLib (alias: filePath) - default: the bundled SpiritLevel PcbLib",+    "timeout": "seconds, default 60"+   },+   "output": {+    "available": "[string] - when the name was not found",+    "dialogs": "[..]",+    "path": "string",+    "selected": "string",+    "shot": "{image: base64 png, width, height}",+    "steps": "[..]",+    "surface": "3d_chip",+    "windowEtiquette": "background"+   },+   "hint": "The proof after altium_add_body / altium_import_part {step}. Pads with no body in the frame: altium_pcb_report to see whether a body exists, altium_normalize_bodies if it sits off the pads.",+   "pitfalls": [+    "Measured live 2026-09-20: it RAISES Altium over the user's work. One call per proof; never loop it.",+    "It leaves the library editor in 3D mode; altium_show_footprint switches back to 2D."+   ],+   "related": [+    "show_footprint",+    "add_body",+    "normalize_bodies",+    "pcb_report"+   ],+   "example": {+    "footprintName": "VL53L5CX",+    "libraryPath": "C:/lib/Adom_ToF.PcbLib"+   },+   "longRunning": false,+   "timeoutSeconds": 120+  },+  {+   "name": "show_footprint",+   "tier": "app",+   "summary": "Open a PcbLib member in the footprint editor in 2D, zoom to fit, and capture a proof frame.",+   "input": {+    "footprintName": "string (alias: name) - default: the first footprint of YOUR library, SO-G3 in the demo library",+    "libraryPath": "path to a .PcbLib (alias: filePath) - default: the bundled SpiritLevel PcbLib",+    "timeout": "seconds, default 60"+   },+   "output": {+    "available": "[string] - when the name was not found",+    "dialogs": "[..]",+    "path": "string",+    "selected": "string",+    "shot": "{image: base64 png, width, height}",+    "steps": "[..]",+    "surface": "footprint",+    "windowEtiquette": "background"+   },+   "hint": "Check pads, silkscreen and courtyard in shot.image; altium_pcb_report {path, name} gives the numbers. altium_show_3d_chip for the body.",+   "pitfalls": [+    "Measured live 2026-09-20: it RAISES Altium over the user's work. One call per proof; never loop it.",+    "A wrong footprintName fails with available[] (up to 50 names): pick from it.",+    "An empty-looking canvas usually means a 3D body sits 50000 mil from the pads: altium_normalize_bodies."+   ],+   "related": [+    "show_3d_chip",+    "pcb_report",+    "build_footprint",+    "list_library"+   ],+   "example": {+    "footprintName": "VL53L5CX",+    "libraryPath": "C:/lib/Adom_ToF.PcbLib"+   },+   "longRunning": false,+   "timeoutSeconds": 120+  },+  {+   "name": "show_library",+   "tier": "app",+   "summary": "Open a .SchLib or .PcbLib (kind taken from the extension), select its first component, return a proof frame and the component names.",+   "input": {+    "libraryPath": "path to a .SchLib/.PcbLib (alias: filePath) - default: the bundled SpiritLevel SchLib",+    "timeout": "seconds, default 60"+   },+   "output": {+    "componentCount": "int",+    "components": "[string] up to 200",+    "dialogs": "[..]",+    "path": "string",+    "selected": "string",+    "shot": "{image: base64 png, width, height}",+    "steps": "[..]",+    "surface": "library",+    "windowEtiquette": "background"+   },+   "hint": "Then altium_show_symbol / altium_show_footprint / altium_show_3d_chip {libraryPath, name} for one member. altium_list_library gives the names without a frame.",+   "pitfalls": [+    "ALWAYS pass libraryPath: the default opens Altium's example library in the user's session.",+    "Measured live 2026-09-20: it RAISES Altium over the user's work. One call per proof; never loop it.",+    "errorCode proof_unavailable = opened and selected correctly, only the capture failed (window minimized)."+   ],+   "related": [+    "list_library",+    "show_symbol",+    "show_footprint",+    "show_3d_chip"+   ],+   "example": {+    "libraryPath": "C:/lib/Adom_ToF.SchLib"+   },+   "longRunning": false,+   "timeoutSeconds": 120+  },+  {+   "name": "show_project",+   "tier": "app",+   "summary": "Open a project (.PrjPcb) through the agent and return a proof frame of Altium's main window. With no filePath it opens Altium's bundled example project (Bluetooth Sentinel).",+   "input": {+    "filePath": "path to a .PrjPcb - default: the bundled Bluetooth Sentinel example",+    "timeout": "seconds, default 60"+   },+   "output": {+    "dialogs": "[..]",+    "dialogsDetected": "int",+    "document": "string",+    "elapsedSec": "float",+    "path": "string",+    "shot": "{image: base64 png, width, height}",+    "steps": "[{open, result, ms}]",+    "surface": "project",+    "title": "string",+    "windowEtiquette": "background"+   },+   "hint": "A project has no document view of its own: follow with altium_show_schematic / altium_show_2d_board {filePath} for something to look at, or altium_compile {projectPath}.",+   "pitfalls": [+    "ALWAYS pass filePath for the user's work: the default opens Altium's example project in their session.",+    "Measured live 2026-09-20: opening documents RAISES Altium over the user's work even though the reply says background. One call per proof; never loop show_* verbs.",+    "success:false with dialogs[] means a box came up during the open: read it before anything else."+   ],+   "related": [+    "show_schematic",+    "show_2d_board",+    "compile",+    "open_project"+   ],+   "example": {+    "filePath": "C:/proj/board.PrjPcb"+   },+   "longRunning": false,+   "timeoutSeconds": 120+  },+  {+   "name": "show_schematic",+   "tier": "app",+   "summary": "Open a .SchDoc, zoom to fit, and return a proof frame. With no filePath it opens the bundled Bluetooth Sentinel top sheet.",+   "input": {+    "filePath": "path to a .SchDoc - default: the bundled example sheet",+    "timeout": "seconds, default 60"+   },+   "output": {+    "dialogs": "[..]",+    "dialogsDetected": "int",+    "document": "string",+    "elapsedSec": "float",+    "path": "string",+    "shot": "{image: base64 png, width, height}",+    "steps": "[{open | process, ...}]",+    "surface": "schematic",+    "title": "string",+    "windowEtiquette": "background"+   },+   "hint": "LOOK at shot.image before claiming anything. Then altium_read_schematic for the data, or altium_animate_view {lane:'sch'} for zoom moves.",+   "pitfalls": [+    "ALWAYS pass filePath: the default opens Altium's example design in the user's session.",+    "Measured live 2026-09-20: it RAISES Altium over the user's work. One call per proof; never loop it.",+    "After re-generating a sheet at the same path, altium_close_document the stale one first or the frame shows the OLD document."+   ],+   "related": [+    "read_schematic",+    "place_on_sch",+    "animate_view",+    "show_2d_board"+   ],+   "example": {+    "filePath": "C:/proj/tof.SchDoc"+   },+   "longRunning": false,+   "timeoutSeconds": 120+  },+  {+   "name": "show_symbol",+   "tier": "app",+   "summary": "Open a SchLib member in the Symbol editor and capture a background proof.",+   "input": {+    "libraryPath": "path",+    "symbolName": "string"+   },+   "output": {+    "shot": "{image: base64 png, width, height}",+    "success": "bool",+    "windowEtiquette": "background"+   },+   "hint": "Never foregrounds: a minimized Altium is restored INTO THE BACKGROUND and the user's window handed straight back.",+   "pitfalls": [+    "A capture under 400px wide is refused as proof_unavailable - a title-bar sliver is not evidence."+   ],+   "related": [+    "show_footprint",+    "show_3d_chip",+    "show_library"+   ],+   "example": {},+   "longRunning": false,+   "timeoutSeconds": 60+  },+  {+   "name": "signin",+   "tier": "app",+   "summary": "Report-only alias of altium_signin_status: it does NOT sign anyone in. Signing in to Altium is a browser flow only the human can complete.",+   "input": {},+   "output": {+    "currentUserProcess": "object - only when the agent is resident",+    "licenceState": "string",+    "licensed": "bool | null",+    "running": "bool",+    "workspaceConnected": "bool",+    "workspaceMenu": "string"+   },+   "hint": "To see WHO is signed in: altium_license {action:'show'} and read the page ('Sign Out' visible = already signed in). Not connected to a workspace -> altium_connect_workspace.",+   "pitfalls": [+    "Despite the name there are no credential arguments and nothing is typed or clicked.",+    "Never ask the user for their Altium password: the browser flow is theirs."+   ],+   "related": [+    "signin_status",+    "license",+    "connect_workspace",+    "install"+   ],+   "example": {},+   "longRunning": false,+   "timeoutSeconds": 300+  },+  {+   "name": "signin_status",+   "tier": "app",+   "summary": "Account and Altium 365 workspace connection as Altium's own UI reports it: licence state from the title, and whether the workspace menu reads 'Not Connected'.",+   "input": {},+   "output": {+    "currentUserProcess": "object - only when the agent is resident",+    "licenceState": "string",+    "licensed": "bool | null",+    "running": "bool",+    "workspace": "null - the name is not read",+    "workspaceConnected": "bool",+    "workspaceMenu": "string"+   },+   "hint": "workspaceConnected:false -> altium_connect_workspace opens the picker. The signed-in account name is on the License Management page: altium_license {action:'show'}.",+   "pitfalls": [+    "workspaceConnected is inferred from the ABSENCE of a 'Not Connected' menu item: a UIA miss reads as connected."+   ],+   "related": [+    "connect_workspace",+    "license",+    "license_status"+   ],+   "example": {},+   "longRunning": false,+   "timeoutSeconds": 60+  },+  {+   "name": "state",+   "tier": "app",+   "summary": "The whole picture in one call: licence state, windows, dialogs, a screenshot of every main/dialog window, and the agent's logical state (focused document, focused project, open documents).",+   "input": {+    "screenshots": "bool, default true"+   },+   "output": {+    "agentAlive": "bool - union over every workspace",+    "agentWorkspace": "path | null - where logical came from",+    "dialogs": "[..]",+    "drivingBadge": "{active}",+    "licence": "{running, licensed, state, adVersion, edition, document, hwnd, pid}",+    "liveWorkspaces": "[{workspace, heartbeatAgeSec}]",+    "logical": "agent reply; data = {focusedDocument, focusedProject, documents:[{path, kind, project, open}]} | null when no agent is alive",+    "shots": "[{title, hwnd, kind, image: base64 png, width, height}]",+    "windows": "[..]"+   },+   "hint": "Look before acting: logical.data.focusedDocument is what the board/schematic read verbs would read; altium_activate_document {path} changes it. Poll with screenshots:false.",+   "pitfalls": [+    "logical is null when no agent is resident: state never bootstraps one. Call any agent-backed verb or altium_agent_start first.",+    "shots[] are full-size base64 PNGs of every main and dialog window; pass screenshots:false when you only need the data."+   ],+   "related": [+    "status",+    "window_info",+    "activate_document",+    "agent_start"+   ],+   "example": {+    "screenshots": false+   },+   "longRunning": false,+   "timeoutSeconds": 60+  },+  {+   "name": "status",+   "tier": "app",+   "summary": "Is Altium running, licensed, and is the resident agent alive?",+   "input": {},+   "output": {+    "agentAlive": "bool",+    "licenceState": "string",+    "licensed": "bool",+    "running": "bool"+   },+   "hint": "Cheapest health call - answers from ab's window list plus the agent heartbeat; never touches the script engine.",+   "pitfalls": [+    "'running' can be true while 'licensed' is false - a trial lease can be stranded by a crash."+   ],+   "related": [+    "readiness",+    "agent_status",+    "instances"+   ],+   "example": {},+   "longRunning": false,+   "timeoutSeconds": 15+  },+  {+   "name": "uninstall",+   "tier": "app",+   "summary": "Remove the bridge's own cache directory (agent workspaces, rendered agent scripts, the Altium 365 token file). Altium Designer and the user's designs are never touched.",+   "input": {+    "dryRun": "bool - report only, delete nothing"+   },+   "output": {+    "dryRun": "bool",+    "note": "string",+    "removed": "[path]"+   },+   "hint": "This is the manifest's uninstall hook; nothing to call afterwards. To revoke the Altium 365 grant remotely call altium_a365_logout BEFORE this.",+   "pitfalls": [+    "A resident agent is stopped even with dryRun:true; only the delete is skipped."+   ],+   "related": [+    "a365_logout",+    "agent_stop"+   ],+   "example": {+    "dryRun": true+   },+   "longRunning": false,+   "timeoutSeconds": 30+  },+  {+   "name": "window_info",+   "tier": "app",+   "summary": "Altium's top-level windows plus any open dialogs, without screenshots. The cheap way to ask 'is a modal blocking the engine?'.",+   "input": {},+   "output": {+    "dialogs": "[{hwnd, title, body, buttons, category, safeAction, ownedByMainInstance}]",+    "hasModalDialogs": "bool - a dialog owned by the main instance",+    "windows": "[{hwnd, title, kind, className, pid, rect}]"+   },+   "hint": "hasModalDialogs:true means the agent loop and every -R request are frozen behind it: altium_check_dialogs for the pictures, then altium_dismiss_dialogs {button, titles}. Otherwise the main window's title names the current document.",+   "pitfalls": [+    "Altium PAINTS its dialog text, so body is often empty here; altium_check_dialogs attaches a PNG per dialog."+   ],+   "related": [+    "check_dialogs",+    "dismiss_dialogs",+    "state"+   ],+   "example": {},+   "longRunning": false,+   "timeoutSeconds": 15+  },+  {+   "name": "zoom_to",+   "tier": "app",+   "summary": "Frame the PCB view on a rectangle, a part (or several) or a whole net, with a margin.",+   "input": {+    "margin": "mm (default 1)",+    "net": "(optional) a net: its pads, tracks and vias",+    "rect": "(optional) [x1, y1, x2, y2] mm",+    "ref": "(optional) a designator, e.g. U1",+    "refs": "(optional) [designator]"+   },+   "output": {+    "rect": "[left, bottom, right, top] mm framed"+   },+   "hint": "For a close look at a joint or a camera shot of one part or net; altium_run_command PCB:Zoom Action=All frames the whole board.",+   "pitfalls": [+    "Needs the board current.",+    "Coordinates are Altium's (y up), as routing_state reports them."+   ],+   "related": [+    "routing_state",+    "run_command",+    "animate_view"+   ],+   "example": {+    "margin": 0.5,+    "net": "SW"+   },+   "longRunning": false,+   "timeoutSeconds": 60+  },+  {+   "name": "a365_configure",+   "tier": "platform",+   "summary": "Store the Altium 365 workspace domain and, optionally, a Bearer token created by hand in the workspace web UI (Admin - Developer). Local-only; tokens never appear in replies.",+   "input": {+    "token": "string - optional developer token",+    "workspace": "string - e.g. acme.365.altium.com (required unless already stored)"+   },+   "output": {+    "hasToken": "bool",+    "workspace": "string"+   },+   "hint": "Then altium_a365_status to prove the token is live. Preferred over a manual token: altium_a365_oauth_begin {clientId, workspace}.",+   "pitfalls": [+    "The token is written to a365.json in the bridge cache: altium_uninstall deletes it, altium_a365_logout removes it.",+    "Never echo a token back to the user or into a log."+   ],+   "related": [+    "a365_status",+    "a365_oauth_begin",+    "a365_query"+   ],+   "example": {+    "workspace": "acme.365.altium.com"+   },+   "longRunning": false,+   "timeoutSeconds": 15+  },+  {+   "name": "a365_files_upload",+   "tier": "platform",+   "summary": "Upload one local file to the workspace's Files Service and get a temporary opaque file id to pass to a library mutation.",+   "input": {+    "filePath": "path on THIS machine (required)",+    "maxBytes": "int, default 104857600 (100 MB)",+    "timeout": "int seconds, default 120"+   },+   "output": {+    "expiresAfterHoursInactive": "24",+    "fileId": "string - opaque"+   },+   "hint": "Pass fileId straight into the matching mutation via altium_a365_query {query, variables}. Do not parse or persist it.",+   "pitfalls": [+    "a365_file_not_found: the path must be on the bridge's machine - stage it with send_files.",+    "The id expires after 24 h of inactivity."+   ],+   "related": [+    "a365_query",+    "a365_library_schema"+   ],+   "example": {+    "filePath": "C:/lib/Adom_ToF.SchLib"+   },+   "longRunning": false,+   "timeoutSeconds": 120+  },+  {+   "name": "a365_library_overview",+   "tier": "platform",+   "summary": "One fixed read-only query: the workspace library's name, description, update time, component ids/names and footprint/symbol totals.",+   "input": {},+   "output": {+    "data": "{desLibrary: {name, description, updatedAt, components: {nodes: [{id, name}]}, footprints: {totalCount}, symbols: {totalCount}}}",+    "errors": "[..] | null"+   },+   "hint": "The proof that a fresh OAuth token is workspace-scoped. Then altium_a365_library_schema before attempting any mutation.",+   "pitfalls": [+    "Same error codes as altium_a365_query (a365_not_configured, a365_graphql_http, a365_network_error)."+   ],+   "related": [+    "a365_status",+    "a365_library_schema",+    "a365_query"+   ],+   "example": {},+   "longRunning": false,+   "timeoutSeconds": 60+  },+  {+   "name": "a365_library_schema",+   "tier": "platform",+   "summary": "Read-only schema map: the GraphQL queries and mutations whose names mention lib, component, symbol, footprint, part or datasheet, with mutation argument types.",+   "input": {+    "timeout": "int seconds, default 60"+   },+   "output": {+    "mutations": "[{name, description, args: [{name, type}]}]",+    "queries": "[{name, description}]"+   },+   "hint": "Pick the mutation, understand its input types AND its rollback/delete counterpart, upload inputs with altium_a365_files_upload, then altium_a365_query.",+   "pitfalls": [+    "A name filter over introspection: a relevant field named differently is not listed - the full schema is at https://<workspace>/api/voyager."+   ],+   "related": [+    "a365_query",+    "a365_files_upload",+    "a365_library_overview"+   ],+   "example": {},+   "longRunning": false,+   "timeoutSeconds": 60+  },+  {+   "name": "a365_logout",+   "tier": "platform",+   "summary": "Revoke the refresh grant at Altium and remove every cached OAuth token. Workspace and client id stay configured.",+   "input": {},+   "output": {+    "revoked": "bool - a remote revocation happened",+    "workspace": "string"+   },+   "hint": "altium_a365_status afterwards should read hasToken:false. Sign in again with altium_a365_oauth_begin.",+   "pitfalls": [+    "If the remote revocation fails (a365_revoke_failed / a365_network_error) the LOCAL tokens are kept on purpose: retry.",+    "This is the Altium 365 API grant only; it does not sign Altium Designer out or release a licence seat."+   ],+   "related": [+    "a365_status",+    "a365_oauth_begin",+    "uninstall"+   ],+   "example": {},+   "longRunning": false,+   "timeoutSeconds": 60+  },+  {+   "name": "a365_oauth_begin",+   "tier": "platform",+   "summary": "Start the Altium 365 OAuth (PKCE + ActionWait) sign-in: the bridge starts listening FIRST, then returns the authorize URL for the user's browser. Works with Altium closed.",+   "input": {+    "clientId": "string - an Altium-issued public OAuth client id (or the stored one)",+    "selectWorkspace": "strict (default) | optional | none",+    "timeout": "int seconds, default 300 - how long to wait for the consent",+    "workspace": "string - e.g. acme.365.altium.com (or the stored one)"+   },+   "output": {+    "authorizeUrl": "string",+    "status": "waiting",+    "workspace": "string"+   },+   "hint": "Open authorizeUrl in the user's EXISTING browser only after this returns, then poll altium_a365_oauth_status until status is authorized.",+   "pitfalls": [+    "a365_client_id_required: OAuth clients come from Altium's Developer Center enrollment. Never reuse a first-party client id.",+    "a365_oauth_in_progress: one flow at a time - altium_a365_oauth_cancel the old one first.",+    "The consent page is the human's step; tokens never appear in any reply."+   ],+   "related": [+    "a365_oauth_status",+    "a365_oauth_cancel",+    "a365_status"+   ],+   "example": {+    "clientId": "<your Altium-issued client id>",+    "workspace": "acme.365.altium.com"+   },+   "longRunning": false,+   "timeoutSeconds": 30+  },+  {+   "name": "a365_oauth_cancel",+   "tier": "platform",+   "summary": "Cancel a waiting OAuth flow.",+   "input": {},+   "output": {+    "alreadyStopped": "bool",+    "status": "cancelling | <current status>"+   },+   "hint": "Poll altium_a365_oauth_status until it reads cancelled, then altium_a365_oauth_begin again if wanted.",+   "pitfalls": [+    "The worker notices the cancel between long-polls (up to about 75 s)."+   ],+   "related": [+    "a365_oauth_begin",+    "a365_oauth_status"+   ],+   "example": {},+   "longRunning": false,+   "timeoutSeconds": 10+  },+  {+   "name": "a365_oauth_status",+   "tier": "platform",+   "summary": "State of the OAuth flow started by altium_a365_oauth_begin: idle | waiting | authorized | cancelled | error.",+   "input": {},+   "output": {+    "claims": "object - unverified token claim summary (authorized)",+    "error": "string (error)",+    "errorCode": "string (error)",+    "status": "idle | waiting | authorized | cancelled | error",+    "workspace": "string"+   },+   "hint": "Poll every few seconds while waiting. authorized -> altium_a365_library_overview proves the workspace-scoped token. error -> read errorCode, then altium_a365_oauth_begin again.",+   "pitfalls": [+    "success is false only for status error; waiting and idle are success:true.",+    "State lives in bridge memory: a bridge restart mid-flow reads idle."+   ],+   "related": [+    "a365_oauth_begin",+    "a365_oauth_cancel",+    "a365_library_overview"+   ],+   "example": {},+   "longRunning": false,+   "timeoutSeconds": 10+  },+  {+   "name": "a365_query",+   "tier": "platform",+   "summary": "Run one GraphQL query or mutation against the workspace's GraphQL gateway (https://<workspace>/svc/napi/gateway/graphql/) with the stored token.",+   "input": {+    "query": "string - GraphQL (required)",+    "timeout": "int seconds, default 60",+    "variables": "object"+   },+   "output": {+    "data": "object | null",+    "errors": "[..] | null"+   },+   "hint": "Discover fields first with altium_a365_library_schema (or https://<workspace>/api/voyager). a365_graphql_http / a365_network_error -> altium_a365_status, then altium_a365_refresh.",+   "pitfalls": [+    "success is false whenever the reply carries errors[], even with partial data.",+    "Mutations change the user's shared workspace for everyone: know the rollback/delete mutation before running one.",+    "a365_not_configured = no workspace or token stored."+   ],+   "related": [+    "a365_library_schema",+    "a365_library_overview",+    "a365_refresh",+    "a365_files_upload"+   ],+   "example": {+    "query": "{ desProjects(first: 10) { nodes { name } } }"+   },+   "longRunning": false,+   "timeoutSeconds": 60+  },+  {+   "name": "a365_refresh",+   "tier": "platform",+   "summary": "Exchange the stored refresh token for a new access token (and store a rotated refresh token if one comes back).",+   "input": {},+   "output": {+    "claims": "object - unverified token claim summary",+    "workspace": "string"+   },+   "hint": "Call when altium_a365_query answers a365_graphql_http 401 or altium_a365_status shows tokenLive:false, then retry.",+   "pitfalls": [+    "a365_refresh_not_configured = no OAuth grant stored (a manual developer token cannot be refreshed).",+    "a365_refresh_failed usually means the grant was revoked: altium_a365_oauth_begin again."+   ],+   "related": [+    "a365_status",+    "a365_query",+    "a365_oauth_begin"+   ],+   "example": {},+   "longRunning": false,+   "timeoutSeconds": 60+  },+  {+   "name": "a365_status",+   "tier": "platform",+   "summary": "Is the Altium 365 lane configured, is there a token, and is it LIVE (probed with a one-row GraphQL query)?",+   "input": {},+   "output": {+    "clientConfigured": "bool",+    "configured": "bool",+    "endpoint": "string - the GraphQL URL",+    "hasRefreshToken": "bool",+    "hasToken": "bool",+    "probe": "object - probe data or error",+    "tokenLive": "bool",+    "workspace": "string | null"+   },+   "hint": "hasToken:false -> altium_a365_oauth_begin (or altium_a365_configure {token}). tokenLive:false with hasRefreshToken -> altium_a365_refresh. Live -> altium_a365_library_overview / altium_a365_query.",+   "pitfalls": [+    "Independent of Altium Designer: this lane works while X2.exe is closed and says nothing about the desktop licence seat."+   ],+   "related": [+    "a365_oauth_begin",+    "a365_refresh",+    "a365_query",+    "a365_configure"+   ],+   "example": {},+   "longRunning": false,+   "timeoutSeconds": 60+  },+  {+   "name": "compile_intlib",+   "tier": "platform",+   "summary": "Compile a .LibPkg into an integrated library (.IntLib).",+   "input": {+    "libpkgPath": "path to a .LibPkg (required)",+    "output": "destination directory",+    "timeout": "seconds, default 300"+   },+   "output": {+    "data": "{libpkg, outputDir}",+    "roundtripMs": "int"+   },+   "hint": "Then altium_install_library {libraryPath: <the .IntLib>}. Check the file exists: the reply does not verify it was written.",+   "pitfalls": [+    "The LibPkg is opened as a project in the user's workspace and stays open.",+    "success only means the compile was dispatched; verify the .IntLib on disk."+   ],+   "related": [+    "extract_intlib",+    "install_library"+   ],+   "example": {+    "libpkgPath": "C:/lib/Adom_ToF.LibPkg",+    "output": "C:/lib/out"+   },+   "longRunning": false,+   "timeoutSeconds": 300+  },+  {+   "name": "extract_intlib",+   "tier": "platform",+   "summary": "Extract the editable .SchLib/.PcbLib sources from a compiled .IntLib (Altium writes them next to the IntLib and opens them).",+   "input": {+    "intlibPath": "path to an .IntLib (required)",+    "timeout": "seconds, default 300"+   },+   "output": {+    "data": "{intlib, sources: [path]}",+    "roundtripMs": "int"+   },+   "hint": "Then altium_list_library / altium_read_symbol on the extracted sources.",+   "pitfalls": [+    "sources[] lists EVERY .SchLib/.PcbLib in any open project, not only the ones this call extracted."+   ],+   "related": [+    "compile_intlib",+    "list_library",+    "install_library"+   ],+   "example": {+    "intlibPath": "C:/lib/Vendor.IntLib"+   },+   "longRunning": false,+   "timeoutSeconds": 300+  },+  {+   "name": "import_kicad",+   "tier": "platform",+   "summary": "Import a KiCad file with Altium's own KiCad importer, driven through the AdomBridge .NET extension.",+   "input": {+    "filePath": "path to a .kicad_sym / .kicad_sch / .kicad_pcb (required)",+    "mode": "string, default 'Import'",+    "timeout": "seconds, default 300"+   },+   "output": {+    "elapsedSec": "float",+    "result": "string - the extension's result file"+   },+   "hint": "Output lands in 'Imported <stem>' beside the source. Then link footprints with altium_set_params {footprint}. For Adom parts prefer altium_import_part, which needs no extension.",+   "pitfalls": [+    "Footprints are NOT imported by Altium's KiCad importer.",+    "Same extension caveat as altium_export_step: on AD 26.9.1 it answered extension_timeout."+   ],+   "related": [+    "extension_ping",+    "install_extension",+    "import_part",+    "set_params"+   ],+   "example": {+    "filePath": "C:/lib/VL53L5CX.kicad_sym"+   },+   "longRunning": false,+   "timeoutSeconds": 300+  },+  {+   "name": "outjob_info",+   "tier": "platform",+   "summary": "An OutJob's containers (output media), the outputs in each, and their key settings (units, decimals, plotted layer count), read from the .OutJob file: Altium need not be running.",+   "input": {+    "outjobPath": "path to the .OutJob (required)"+   },+   "output": {+    "groups": "[{name, variant, containers: [{name, type, outputs}], outputs: [{type, name, document, enabled, inMedia, settings}]}]"+   },+   "hint": "Run a container with altium_run_outjob {outjobPath, projectPath, medium:'<container name>'}.",+   "pitfalls": [+    "Reads the saved file: an OutJob open in Altium with unsaved edits is read as saved."+   ],+   "related": [+    "run_outjob",+    "export_manufacturing"+   ],+   "example": {+    "outjobPath": "C:/proj/board.OutJob"+   },+   "longRunning": false,+   "timeoutSeconds": 30+  },+  {+   "name": "run_outjob",+   "tier": "platform",+   "summary": "Run one output medium of an .OutJob (gerbers, drill, BOM, PDFs...) and list what it wrote under 'Project Outputs for <project>'.",+   "input": {+    "medium": "string, default 'Folder Structure' - the OutputMedium name inside the OutJob",+    "objectKind": "string, default 'OutputBatch'",+    "outjobPath": "path to an .OutJob (alias: filePath) - default: the bundled example Fabrication.OutJob",+    "projectPath": "path to the .PrjPcb the OutJob belongs to; opened first",+    "timeout": "seconds, default 300"+   },+   "output": {+    "agent": "{outjob, medium, objectKind}",+    "dialogs": "[..]",+    "elapsedSec": "float",+    "files": "[relative paths] up to 60",+    "medium": "string",+    "outjob": "path",+    "outputDir": "path | null",+    "windowEtiquette": "background"+   },+   "hint": "files[] is the proof. Pull what you need with ab's pull_file. OutJobs OPEN what they generate (CAMtastic, PDFs): altium_state afterwards and altium_close_document the viewers.",+   "pitfalls": [+    "Pass projectPath with your own OutJob: outputDir/files are looked up beside the PROJECT, and without it beside the OutJob.",+    "medium must be the name of an output medium inside the OutJob. The agent cannot tell whether it ran: files[] is the only proof.",+    "A dialog in dialogs[] means a medium needed a decision (printer, PDF path): read dialogs[].shot."+   ],+   "related": [+    "export_gerber",+    "export_bom",+    "export_step",+    "check_dialogs"+   ],+   "example": {+    "medium": "Folder Structure",+    "outjobPath": "C:/proj/Fabrication.OutJob",+    "projectPath": "C:/proj/board.PrjPcb"+   },+   "longRunning": false,+   "timeoutSeconds": 600+  },+  {+   "name": "variants",+   "tier": "platform",+   "summary": "Assembly variants of a project (which parts are fitted on which build): list them, make or change one with not-fitted parts (or put parts back), or remove one. Altium's script API cannot create variants, so the bridge writes them into the .PrjPcb, reloads the project and reads back what Altium holds.",+   "input": {+    "fitted": "[designators] to put back (their variation is dropped)",+    "name": "variant name; omit to list",+    "notFitted": "[designators] to mark Not Fitted (alias dnp)",+    "projectPath": "path to the .PrjPcb (required)",+    "remove": "bool - delete the variant"+   },+   "output": {+    "altium": "{variants:[{name, variations:[{designator, kind}]}]} as Altium reads them (kind 1 = not fitted, 2 = alternate)",+    "variants": "[{name, notFitted, alternates}]"+   },+   "hint": "altium_bom {variant} then gives that build's BOM (not-fitted parts dropped). Saves the project's documents, closes and reopens the project to load the change.",+   "pitfalls": [+    "Variants with parameter variations are refused (edit those in Altium's Variant Management).",+    "Parts are named by their sheet's unique id: on a multi-channel (repeated) sheet every channel gets the same variation.",+    "Alternate parts are listed but not created here."+   ],+   "related": [+    "bom",+    "export_manufacturing",+    "run_outjob"+   ],+   "example": {+    "name": "Lite",+    "notFitted": [+     "R5",+     "C9",+     "J2"+    ],+    "projectPath": "C:/proj/board.PrjPcb"+   },+   "longRunning": false,+   "timeoutSeconds": 300+  },+  {+   "name": "workspace_place",+   "tier": "platform",+   "summary": "Put a part from the Altium 365 workspace on a sheet: search, pick a result, place it (Altium's own placement, so the part keeps its workspace link, parameters and footprint), move it to x,y and name it.",+   "input": {+    "designator": "optional, else Altium's ('C?')",+    "pick": "result index (0-based) or text its name contains; default the first",+    "query": "the search (required; an MPN finds one part)",+    "save": "bool, default true",+    "sheet": "path to the .SchDoc (required)",+    "x": "mils",+    "y": "mils"+   },+   "output": {+    "picked": "{name, description, footprint}",+    "placed": "{designator, libReference, comment, itemGuid, sourceLibrary, xMil, yMil}"+   },+   "hint": "Then altium_annotate (for a '?'), altium_sch_connect, altium_update_pcb. A part from a local .SchLib goes on with altium_sch_place_part instead.",+   "pitfalls": [+    "Takes 15-70 s. Uses real clicks (double-click the result, click the sheet): Altium comes to the front.",+    "The part lands near the view's lower-left corner first, then moves to x,y: wires do not follow, so connect it after."+   ],+   "related": [+    "workspace_search",+    "sch_place_part",+    "annotate",+    "sch_connect",+    "update_pcb"+   ],+   "example": {+    "query": "100nF 0402 50V",+    "sheet": "C:/proj/Main.SchDoc",+    "x": 3000,+    "y": 2000+   },+   "longRunning": false,+   "timeoutSeconds": 300+  },+  {+   "name": "workspace_search",+   "tier": "platform",+   "summary": "Search the Altium 365 workspace's components (the shared cloud library Altium is signed in to) through Altium's own Components panel: name, description and footprint per result.",+   "input": {+    "limit": "int, default 25",+    "query": "text: an MPN, a value and package ('100nF 0402'), a part name (required)"+   },+   "output": {+    "count": "int",+    "results": "[{index, name, description, footprint}]"+   },+   "hint": "Then altium_workspace_place {sheet, query, pick} puts one on a sheet. Altium's 365 API is a closed beta; the signed-in Altium reaches the workspace itself (altium_signin_status).",+   "pitfalls": [+    "Takes 5-40 s (Altium's WPF panel answers UI Automation slowly; slower when the panel must first be opened).",+    "It types into the panel's search box: Altium comes to the front."+   ],+   "related": [+    "workspace_place",+    "signin_status",+    "bom"+   ],+   "example": {+    "query": "100nF 0402"+   },+   "longRunning": false,+   "timeoutSeconds": 180+  }+ ]+}