skill
EDA Bridge Standard: one way for an AI to drive every PCB tool
Public Made by Adomby adom
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.
← Commit history
0.32.0: reads Altium 0.2.93 and KiCad 1.0.107; Altium's 11 blanks filled
5 files changed
+100−50
README.md+6−5@@ -60,12 +60,12 @@ verb by verb: [docs/coverage.md](docs/coverage.md). <!-- coverage:start (tools/generate.py writes this) --> | Tier | Verbs | Altium | KiCad | Fusion 360 | |---|---|---|---|---|-| **core** | 99 | 95 | 99 | 96 |-| **native** | 55 | 53 | 54 | 52 |-| **checks** | 39 | 38 | 39 | 39 |-| **app** | 70 | 66 | 59 | 62 |+| **core** | 99 | 99 | 99 | 96 |+| **native** | 55 | 54 | 54 | 52 |+| **checks** | 39 | 39 | 39 | 39 |+| **app** | 70 | 70 | 59 | 62 | | **platform** | 40 | 18 | 0 | 23 |-| standard (all but platform) | 263 | 252 | 251 | 249 |+| standard (all but platform) | 263 | 262 | 251 | 249 | <!-- coverage:end --> The **checks** tier is where the other bridges are furthest behind, and it is the cheapest to close: those verbs are@@ -158,6 +158,7 @@ Decisions so far (proposals from the KiCad and Fusion bridges, 2026-10-27; issue ## Status +Version 0.32.0: reads Altium 0.2.93 and KiCad 1.0.107. Altium's 11 blank cells are filled: 9 exact, extensions partial, physical_properties unsupported. Version 0.31.9: reads Fusion 1.13.64; its edit_tracks is exact. Version 0.31.8: KiCad's cancel_run is exact (a real Freerouting stop proven on 1.0.100). Version 0.31.7: the board model names tracks[].id, the handle edit_tracks takes.
docs/coverage.md+17−17@@ -1,17 +1,17 @@ # Coverage: every standard verb on each bridge -Altium bridge 0.2.89 (the reference), KiCad bridge 1.0.100, Fusion 360 bridge 1.13.64. 100 verbs came from other bridges' proposals (tools/proposed/); they are marked *proposed*.+Altium bridge 0.2.93 (the reference), KiCad bridge 1.0.107, Fusion 360 bridge 1.13.64. 100 verbs came from other bridges' proposals (tools/proposed/); they are marked *proposed*. `exact` is the same job under the same or an equivalent name; `partial` does part of it or does it differently (`verbs/coverage.json` names what in `note`); `unsupported` answers `not_supported` for the whole job and is not counted; blank is missing. Generated by `tools/generate.py` from `verbs/coverage.json`; do not edit by hand. | Tier | Verbs | Altium | KiCad | Fusion 360 | |---|---|---|---|---|-| **core** | 99 | 95 | 99 | 96 |-| **native** | 55 | 53 | 54 | 52 |-| **checks** | 39 | 38 | 39 | 39 |-| **app** | 70 | 66 | 59 | 62 |+| **core** | 99 | 99 | 99 | 96 |+| **native** | 55 | 54 | 54 | 52 |+| **checks** | 39 | 39 | 39 | 39 |+| **app** | 70 | 70 | 59 | 62 | | **platform** | 40 | 18 | 0 | 23 |-| standard (all but platform) | 263 | 252 | 251 | 249 |+| standard (all but platform) | 263 | 262 | 251 | 249 | ## core @@ -55,7 +55,7 @@ Read and edit the design. Every bridge provides these: they are how an AI sees a | `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. | `altium_part_parameters` | `kicad_part_parameters` | `fusion_part_parameters` | | `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. | `altium_place_on_pcb` | `kicad_place_footprint / kicad_add_footprint` | `fusion_test_board` (partial) | | `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... | `altium_place_on_sch` | `kicad_place_on_sch` | `fusion_make_showcase_project` (partial) |-| `place_parts` *proposed* | Put footprints from a library onto the current board, each with its position, rotation and side, and read them back. One undo step. | | `kicad_place_parts` | `fusion_place_parts` (partial) |+| `place_parts` *proposed* | Put footprints from a library onto the current board, each with its position, rotation and side, and read them back. One undo step. | `altium_place_parts` | `kicad_place_parts` | `fusion_place_parts` (partial) | | `place_vias` | Place vias (through, top to bottom). One undo step, read back. | `altium_place_vias` | `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... | `altium_plate_vias` | `kicad_plate_vias` | `fusion_plate_vias` | | `project_create` *proposed* | Create a new project folder with a project file, an empty schematic and an empty board (2 to 32 copper layers), with a title and text variables. | `altium_project_create` | `kicad_project_create` | `fusion_project_create` (partial) |@@ -91,9 +91,9 @@ Read and edit the design. Every bridge provides these: they are how an AI sees a | `sch_hierarchy` *proposed* | The sheet tree of a design, recursively: each sheet's name, file, id, instance path, page number, position, size and pins. renumber:true renumbers pages... | `altium_sch_hierarchy` | `kicad_sch_hierarchy` | `fusion_sch_hierarchy` (partial) | | `sch_items` *proposed* | List a sheet's wiring items with ids and coordinates: wires, buses, bus entries, net labels (local, global, hierarchical), power ports, junctions,... | `altium_sch_items` | `kicad_sch_items` | `fusion_sch_items` | | `sch_junction` *proposed* | Add junction dots at points, or remove dots by id or point. One undo step. | `altium_sch_junction` | `kicad_sch_junction` | `fusion_sch_junction` |-| `sch_label` *proposed* | Put a net label on an existing wire at a point, or delete one. sch_connect labels a pin through a new stub, and sch_edit_label changes labels that already... | | `kicad_sch_label` | `fusion_sch_label` (partial) |+| `sch_label` *proposed* | Put a net label on an existing wire at a point, or delete one. sch_connect labels a pin through a new stub, and sch_edit_label changes labels that already... | `altium_sch_label` | `kicad_sch_label` | `fusion_sch_label` (partial) | | `sch_move_region` *proposed* | Move everything inside a box by (dx, dy): parts, wire ends inside the box (wires crossing its edge stretch), labels, junctions, no-connect markers, texts,... | `altium_sch_move_region` | `kicad_sch_move_region` | `fusion_sch_move_region` |-| `sch_no_connect` *proposed* | Put a no-connect marker on a sheet, at a point or on a pin, or remove one by its sch_items id. One undo step. | | `kicad_sch_no_connect` | unsupported |+| `sch_no_connect` *proposed* | Put a no-connect marker on a sheet, at a point or on a pin, or remove one by its sch_items id. One undo step. | `altium_sch_no_connect` | `kicad_sch_no_connect` | unsupported | | `sch_page` *proposed* | Read or set a sheet's page: paper size, orientation and title block (title, date, revision, company, comment lines). | `altium_sch_page` | `kicad_sch_page` | `fusion_sch_page` (partial) | | `sch_pins` *proposed* | Every pin of the named parts on a sheet: its absolute position (rotation and mirror applied), number, name, electrical type, the side it leaves the body,... | `altium_sch_pins` | `kicad_sch_pins` | `fusion_sch_pins` | | `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... | `altium_sch_place_part` | `kicad_sch_place_part` | `fusion_sch_place_part` |@@ -104,7 +104,7 @@ Read and edit the design. Every bridge provides these: they are how an AI sees a | `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:... | `altium_sch_swap_part` | `kicad_sch_swap_part` | `fusion_sch_swap_part` | | `sch_text` *proposed* | Add free text to a sheet (a note, a title, a design comment) with size, angle, justification, bold and italic. Text has no electrical meaning. One undo step. | `altium_sch_text` | `kicad_sch_text` | `fusion_sch_text` | | `sch_trace_point` *proposed* | Everything electrically connected at a point on a sheet: the wires, labels, pins, junctions, sheet pins and no-connect markers there, and for each net they... | `altium_sch_trace_point` | `kicad_sch_trace_point` | `fusion_sch_trace_point` |-| `sch_wire` *proposed* | Draw a wire through given points on a sheet, in the same units as the other sch_* wiring verbs. One undo step. | | `kicad_sch_wire` | `fusion_sch_wire` |+| `sch_wire` *proposed* | Draw a wire through given points on a sheet, in the same units as the other sch_* wiring verbs. One undo step. | `altium_sch_wire` | `kicad_sch_wire` | `fusion_sch_wire` | | `schematic_data` | Components on a schematic sheet (filePath, else the CURRENT sheet) with their pins (number, name, electrical type), position and orientation. designator... | `altium_schematic_data` | `kicad_schematic_data` | `fusion_schematic_data` (partial) | | `set_layer_count` *proposed* | Set the board's copper layer count (2, 4, 6 or 8): the layer table and the stack-up, keeping the board thickness. | `altium_set_layer_count` | `kicad_set_layer_count` | `fusion_set_layer_count` | | `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). | `altium_set_models` | `kicad_set_models` | `fusion_set_models` |@@ -136,7 +136,7 @@ Wraps the EDA tool's own feature (its DRC, ERC, Gerbers, pours, Update PCB). Eve | `create_part_project` *proposed* | Generate a small real project (schematic and board) around one library part, so the part can be shown and tested in every view: schematic, 2D board and 3D... | `altium_create_part_project` | `kicad_create_part_project` (partial) | `fusion_make_test_project` (partial) | | `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... | `altium_diff_pairs` | `kicad_diff_pairs` (partial) | `fusion_diff_pairs` (partial) | | `edit_pours` *proposed* | Change existing pours in place without redrawing them: clearance, minimum width, thermal relief (on/off, spoke width, gap), priority or rank, the net, and... | `altium_edit_pours` | `kicad_edit_pours` | `fusion_edit_pours` (partial) |-| `edit_tracks` *proposed* | Change given tracks by the handle routing_state returns: width, layer and ends. One undo step, read back. | | `kicad_edit_tracks` | `fusion_edit_tracks` |+| `edit_tracks` *proposed* | Change given tracks by the handle routing_state returns: width, layer and ends. One undo step, read back. | `altium_edit_tracks` | `kicad_edit_tracks` | `fusion_edit_tracks` | | `export_3d` *proposed* | Export the board's 3D model in a mesh or CAD format other than STEP (export_step has STEP), through the tool's own exporter, with an optional web-optimized... | `altium_export_3d` | `kicad_export_3d` | `fusion_export_3d` | | `export_bom` | Bill of materials export: altium_run_outjob with the assembly OutJob and the 'Folder Structure' medium as defaults. | `altium_export_bom` | `kicad_export_bom` | `fusion_export_bom` | | `export_dxf` *proposed* | Export chosen board layers to DXF, one file per layer (the outline, panel drawings, stencils, enclosure work). | `altium_export_dxf` | `kicad_export_dxf` | `fusion_export_dxf` |@@ -159,7 +159,7 @@ Wraps the EDA tool's own feature (its DRC, ERC, Gerbers, pours, Update PCB). Eve | `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... | `altium_netlist` | `kicad_netlist` | `fusion_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... | `altium_panelize` | `kicad_panelize` (partial) | `fusion_panelize` | | `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... | `altium_pcb_report` | `kicad_pcb_report` | `fusion_pcb_report` |-| `physical_properties` *proposed* | Per component: volume, mass, density, surface area and centre of mass, from the tool's own materials. A tool with no materials or part masses answers... | | unsupported | `fusion_physical_properties` |+| `physical_properties` *proposed* | Per component: volume, mass, density, surface area and centre of mass, from the tool's own materials. A tool with no materials or part masses answers... | unsupported | unsupported | `fusion_physical_properties` | | `pour_state` | Every polygon pour with the copper it actually poured: filled area per pour and per layer, and the pours that poured nothing. | `altium_pour_state` | `kicad_pour_state` | `fusion_pour_state` | | `project_snapshot` *proposed* | A dated copy of the project's documents into <project>/snapshots/<stamp>[-label]/, with PDFs of the schematic and the board made by the tool. | `altium_project_snapshot` | `kicad_project_snapshot` | `fusion_project_snapshot` (partial) | | `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. | `altium_remove_pours` | `kicad_remove_zone` | `fusion_remove_pours` |@@ -210,7 +210,7 @@ Engineering computed from the board model, not by the tool: one shared engine, s | `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... | `altium_jlcpcb_order` | `kicad_jlcpcb_order` | `fusion_jlcpcb_order` | | `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... | `altium_length_tune` | `kicad_length_tune` | `fusion_length_tune` | | `lint_library` | Check a .PcbLib or .SchLib before it ships: footprints without pads, courtyard, silkscreen or 3D body, with duplicate or missing pad numbers, an origin off... | `altium_lint_library` | `kicad_lint_library` | `fusion_lint_library` |-| `model_check` *proposed* | Per part: is there a 3D model, where is it, is the file found, and does it hold geometry. For a library footprint or a whole board. Read only. | | `kicad_model_check` | `fusion_model_check` (partial) |+| `model_check` *proposed* | Per part: is there a 3D model, where is it, is the file found, and does it hold geometry. For a library footprint or a whole board. Read only. | `altium_model_check` | `kicad_model_check` | `fusion_model_check` (partial) | | `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... | `altium_net_lengths` | `kicad_net_lengths` | `fusion_net_lengths` | | `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... | `altium_pin_pad_check` | `kicad_pin_pad_check` | `fusion_pin_pad_check` | | `place_decoupling` *proposed* | Plan positions for decoupling capacitors beside an IC's power pins (each cap just outside the courtyard, its power pad facing the pin), answered as... | `altium_place_decoupling` | `kicad_place_decoupling` | `fusion_place_decoupling` |@@ -239,7 +239,7 @@ Driving the application itself (launch, licence, dialogs, windows, views). Each | `agent_status` | Is the agent loop alive and responsive, in which workspace, and is any loop resident anywhere? Read-only; never starts the agent. | `altium_agent_status` | unsupported | `fusion_addin_status` (partial) | | `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). | `altium_agent_stop` | unsupported | unsupported | | `animate_view` | Camera/zoom choreography on the current document - native Altium camera commands, animated by Altium itself. | `altium_animate_view` | `kicad_animate_view` | `fusion_view` (partial) |-| `cancel_run` *proposed* | Stop the bridge's own long job, between steps, keeping what is done. With nothing to stop it answers cancelRequested:false and a reason. It never claims to... | | `kicad_cancel_run` | `fusion_cancel_run` (partial) |+| `cancel_run` *proposed* | Stop the bridge's own long job, between steps, keeping what is done. With nothing to stop it answers cancelRequested:false and a reason. It never claims to... | `altium_cancel_run` | `kicad_cancel_run` | `fusion_cancel_run` (partial) | | `check_dialogs` | List every Altium dialog currently open, classified, each with a screenshot (the only way to READ one: Altium paints its dialog text). | `altium_check_dialogs` | `kicad_check_dialogs` | `fusion_check_dialogs` | | `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... | `altium_close` | `kicad_close` | `fusion_stop` | | `close_all_documents` *proposed* | Close every open document without a save dialog. Unsaved work is never lost by default: a document with unsaved changes stays open and is listed, unless... | `altium_close_all_documents` | `kicad_close_all_documents` | `fusion_close_all_documents` |@@ -256,7 +256,7 @@ Driving the application itself (launch, licence, dialogs, windows, views). Each | `eval` | Run a DelphiScript snippet inside Altium (optional Var block, then Begin ... End;) on the CURRENT document, or on filePath opened first. | `altium_eval` | `kicad_eval` | `fusion_run_modeling_script` (partial) | | `export_spice` *proposed* | Write the design's SPICE netlist with the tool's own exporter, and say what it holds. | `altium_export_spice` | `kicad_export_spice` | `fusion_export_spice` | | `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. | `altium_extension_ping` | unsupported | `fusion_addin_status` (partial) |-| `extensions` *proposed* | The tool's own plugins and add-ons: list what is installed, install one, remove one. Not install_extension, which is the bridge's own helper inside the tool. | | `kicad_extensions` | `fusion_extensions` (partial) |+| `extensions` *proposed* | The tool's own plugins and add-ons: list what is installed, install one, remove one. Not install_extension, which is the bridge's own helper inside the tool. | `altium_extensions` (partial) | `kicad_extensions` | `fusion_extensions` (partial) | | `foreground` | Bring Altium in front of the user - the sanctioned exception to the never-foreground rule. | `altium_foreground` | `kicad_foreground` | `fusion_foreground` | | `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... | `altium_install` | `kicad_upgrade` (partial) | `fusion_install` | | `install_extension` | Install, update or remove the AdomBridge .NET extension (the PowerShell installer shipped beside the bridge). Altium must be CLOSED. | `altium_install_extension` | unsupported | unsupported |@@ -271,7 +271,7 @@ Driving the application itself (launch, licence, dialogs, windows, views). Each | `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. | `altium_open_document` | `kicad_open_document` | `fusion_open_document` | | `open_project` | Open a project (.PrjPcb / .PrjScr / .PrjMbd / .PrjHar / .LibPkg) by path. Same handler as altium_open_document. | `altium_open_project` | `kicad_open_project` | `fusion_open_cloud_file` (partial) | | `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... | `altium_panel` | `kicad_send_key` (partial) | `fusion_panel` |-| `preferences` *proposed* | Read the tool's own settings (all, or one by key), or change them with set. Reports per key what was applied. A tool that rewrites its settings on exit... | | `kicad_preferences` | `fusion_preferences` |+| `preferences` *proposed* | Read the tool's own settings (all, or one by key), or change them with set. Reports per key what was applied. A tool that rewrites its settings on exit... | `altium_preferences` | `kicad_preferences` | `fusion_preferences` | | `prewarm` *proposed* | Get this machine ready for a fast first call: detect the tool, seed its first-run settings, dismiss first-run wizards and repair what the first real call... | `altium_prewarm` | `kicad_prewarm` | `fusion_prewarm` | | `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. | `altium_progress` | `kicad_progress` | `fusion_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. | `altium_readiness` | `kicad_readiness` | `fusion_readiness` |@@ -279,7 +279,7 @@ Driving the application itself (launch, licence, dialogs, windows, views). Each | `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. | `altium_run_script` | `kicad_run_script` | `fusion_run_script` | | `save` | Save the focused document, or the document at filePath (opened first if need be, saved by its path, never whatever happens to have focus). | `altium_save` | `kicad_save` | `fusion_save` | | `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... | `altium_sch_zoom` | `kicad_sch_zoom` (partial) | `fusion_sch_zoom` |-| `select_items` *proposed* | Select parts by designator on the board or the sheet, or clear the selection. A bridge that cannot select on one of them refuses it by name. verified says... | | `kicad_select_items` (partial) | `fusion_select_items` (partial) |+| `select_items` *proposed* | Select parts by designator on the board or the sheet, or clear the selection. A bridge that cannot select on one of them refuses it by name. verified says... | `altium_select_items` | `kicad_select_items` (partial) | `fusion_select_items` (partial) | | `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. | `altium_select_net` | `kicad_select_net` | `fusion_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... | `altium_selection` | `kicad_selection` (partial) | unsupported | | `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... | `altium_selftest_script_error` | unsupported | unsupported |
package.json+1−1@@ -1,6 +1,6 @@ { "slug": "eda-bridge-standard",- "version": "0.31.9",+ "version": "0.32.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.",
verbs/coverage.json+49−14@@ -1,12 +1,12 @@ {- "standard": "0.31.9",+ "standard": "0.32.0", "reference": { "bridge": "altium",- "version": "0.2.89"+ "version": "0.2.93" }, "proposed": 100, "compared": {- "kicad": "1.0.100",+ "kicad": "1.0.107", "fusion360": "1.13.64" }, "tiers": {@@ -647,7 +647,10 @@ "name": "place_parts", "tier": "core", "source": "proposed",- "altium": null,+ "altium": {+ "verb": "altium_place_parts",+ "match": "exact"+ }, "kicad": { "verb": "kicad_place_parts", "match": "exact"@@ -1265,7 +1268,10 @@ "name": "sch_label", "tier": "core", "source": "proposed",- "altium": null,+ "altium": {+ "verb": "altium_sch_label",+ "match": "exact"+ }, "kicad": { "verb": "kicad_sch_label", "match": "exact"@@ -1297,7 +1303,10 @@ "name": "sch_no_connect", "tier": "core", "source": "proposed",- "altium": null,+ "altium": {+ "verb": "altium_sch_no_connect",+ "match": "exact"+ }, "kicad": { "verb": "kicad_sch_no_connect", "match": "exact"@@ -1489,7 +1498,10 @@ "name": "sch_wire", "tier": "core", "source": "proposed",- "altium": null,+ "altium": {+ "verb": "altium_sch_wire",+ "match": "exact"+ }, "kicad": { "verb": "kicad_sch_wire", "match": "exact"@@ -1928,7 +1940,10 @@ "name": "edit_tracks", "tier": "native", "source": "proposed",- "altium": null,+ "altium": {+ "verb": "altium_edit_tracks",+ "match": "exact"+ }, "kicad": { "verb": "kicad_edit_tracks", "match": "exact"@@ -2325,7 +2340,11 @@ "name": "physical_properties", "tier": "native", "source": "proposed",- "altium": null,+ "altium": {+ "verb": "altium_physical_properties",+ "match": "unsupported",+ "note": "Altium keeps no material or mass on a part: its 3D bodies hold shape only. altium_bodies gives each part's 3D box and height, altium_layer_stack the board's thickness"+ }, "kicad": { "verb": "kicad_physical_properties", "match": "unsupported",@@ -3090,7 +3109,10 @@ "name": "model_check", "tier": "checks", "source": "proposed",- "altium": null,+ "altium": {+ "verb": "altium_model_check",+ "match": "exact"+ }, "kicad": { "verb": "kicad_model_check", "match": "exact"@@ -3472,7 +3494,10 @@ "name": "cancel_run", "tier": "app", "source": "proposed",- "altium": null,+ "altium": {+ "verb": "altium_cancel_run",+ "match": "exact"+ }, "kicad": { "verb": "kicad_cancel_run", "match": "exact"@@ -3767,7 +3792,11 @@ "name": "extensions", "tier": "app", "source": "proposed",- "altium": null,+ "altium": {+ "verb": "altium_extensions",+ "match": "partial",+ "note": "lists Altium's installed extensions by edition with their versions; install and remove are refused, since Altium does those on its Extensions & Updates page, which is not in UI Automation"+ }, "kicad": { "verb": "kicad_extensions", "match": "exact"@@ -4032,7 +4061,10 @@ "name": "preferences", "tier": "app", "source": "proposed",- "altium": null,+ "altium": {+ "verb": "altium_preferences",+ "match": "exact"+ }, "kicad": { "verb": "kicad_preferences", "match": "exact"@@ -4169,7 +4201,10 @@ "name": "select_items", "tier": "app", "source": "proposed",- "altium": null,+ "altium": {+ "verb": "altium_select_items",+ "match": "exact"+ }, "kicad": { "verb": "kicad_select_items", "match": "partial",
verbs/standard.json+27−13@@ -1,12 +1,12 @@ {- "standard": "0.31.9",+ "standard": "0.32.0", "reference": { "bridge": "altium",- "version": "0.2.89"+ "version": "0.2.93" }, "proposed": 100, "compared": {- "kicad": "1.0.100",+ "kicad": "1.0.107", "fusion360": "1.13.64" }, "tiers": {@@ -7741,13 +7741,15 @@ "reason": "string - optional, recorded in steps" }, "output": {+ "engineFree": "bool - verified by starting a fresh agent", "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."+ "It presses OK only on the script Error box (a lone OK titled Error or X2). Any other dialog, a save prompt included, stops it with blocked_by_dialog: read it with altium_check_dialogs.",+ "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.",+ "agent_halts_on_start = the engine was freed, but the agent stops on its first command every time it starts, so agent verbs cannot run on this Altium: report it with steps[].errorShot. recover never loops on it (#30)." ], "related": [ "check_dialogs",@@ -7759,7 +7761,7 @@ "reason": "agent_timeout on read_board" }, "longRunning": false,- "timeoutSeconds": 60+ "timeoutSeconds": 240 }, { "name": "agent_start",@@ -7773,6 +7775,7 @@ "alreadyRunning": "bool", "generation": "string - AdomAgent_<digest>", "install": "object - the rendered script",+ "parkedLoopStopped": "[step] - an earlier loop was parked in the debugger and was stopped first", "startedInSec": "float", "strayHelpersKilled": "[pid]" },@@ -7780,6 +7783,9 @@ "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.",+ "agent_halted_after_start = the agent came up and stopped on its first command (errorShot is Altium's box). The bridge already stopped the script, so Altium is not left parked; it is a bug in the agent on this Altium build: report it with errorShot (#30).",+ "An earlier loop parked in Altium's script debugger (engineParked) is stopped first, so the new one can run: parkedLoopStopped lists what was done.",+ "If the bridge itself ever ends during a verb, the next bridge process writes which verb and its last steps to altium-bridge-crash.log beside the bridge exe (with Rust's own message for a stack overflow). Attach that file to a report.", "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": [@@ -7800,15 +7806,18 @@ "input": {}, "output": { "alive": "bool - selected instance",+ "engineParked": "bool", "heartbeat": "{ageSec, tick, ...} | null", "liveWorkspaces": "[{workspace, heartbeatAgeSec}]",+ "parkedWorkspaces": "[path] - when parked", "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."+ "residentAnywhere:true with alive:false is a freeze-on-close hazard: altium_close tears every loop down before the app exits.",+ "engineParked:true = a loop started in this Altium and stopped in its script debugger without exiting, whatever document is in front (read from the agent's own files, not the window title). alive is false then, even within a minute of its last heartbeat. altium_agent_recover, or the next agent verb, stops it." ], "related": [ "agent_start",@@ -7916,7 +7925,8 @@ }, "output": { "dialogs": "[{hwnd, pid, title, className, body, buttons, category, safeAction, meaning, ownedByMainInstance, rect, shot: base64 png}]",- "dialogsDetected": "int"+ "dialogsDetected": "int",+ "engineParked": "bool - no dialog, but the agent's script is stopped in Altium's debugger: altium_agent_recover" }, "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": [@@ -8641,16 +8651,18 @@ "source": "altium", "summary": "Drive the License Management page (UIA first - no cursor, no foreground).", "input": {- "action": "show|refresh|use_license|start_trial|sign_out"+ "action": "show|refresh|use_license|release|start_trial|sign_out",+ "product": "use_license, release: part of the licence's name (default the first that isn't the free Viewer)" }, "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.",+ "hint": "One On-Demand seat serves EVERY Altium app on this machine. use_license claims a free seat, and inUse says what the page then marks In Use. release gives it back, and released says the page confirms it. Altium falls back to Viewer and stops running scripts, so the bridge's verbs answer not_licensed until a licence is claimed again.", "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."+ "NEVER kill an Altium holding the seat: the lease strands, the page shows Seats 0/1 while every claim fails. altium_close releases it.",+ "Altium Designer Develop has no licence page the bridge can reach: every action but show and refresh answers licence_page_not_open. altium_close releases its seat." ], "related": [ "ensure_licensed",@@ -8661,7 +8673,7 @@ "action": "use_license" }, "longRunning": false,- "timeoutSeconds": 60+ "timeoutSeconds": 90 }, { "name": "license_status",@@ -8949,6 +8961,7 @@ "agentAlive": "bool - the selected instance", "agentLiveWorkspaces": "[{workspace, heartbeatAgeSec}] - only when a loop lives outside the selection", "agentResidentAnywhere": "bool - any workspace",+ "engineParked": "bool - the agent's script is stopped in Altium's debugger", "install": "{base_dir, exe, system_dir, major} | null", "installed": "bool", "licenceState": "string",@@ -8958,7 +8971,8 @@ }, "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."+ "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.",+ "engineParked:true = the agent's script is stopped on a breakpoint in Altium's debugger, with no dialog: nothing agent-backed runs and Altium refuses to close. altium_agent_recover stops it (#30)." ], "related": [ "status",