app
Altium - the Altium Bridge
Public Made by Adomby adom
Adom Bridge for Altium Designer: a resident DelphiScript agent gives the AI eval, the 1,100-command process launcher, all 8 EDA surfaces with screenshots, compile/ERC, DRC, OutJob outputs, native SchLib/PcbLib authoring with STEP 3D bodies, and dialog handling by PID.
main
John Lauer
0.2.29 source: altium_install verb + detect.installVerb/hostAppOptionalVerbs; battery covers it; install skill starts w…
1b81b35
1mo ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190
//! `altium_install`: install Altium Designer on this machine, staged, with the rules IN the verb.
//!
//! Written 2026-09-20 after an install on a fresh box where the AI driving it kept forgetting the
//! rules: it asked the human to "sign in" on a page that was already signed in (it never looked),
//! it left Release / Assign / Use License / Refresh for the human although those are ordinary
//! clicks, it never toasted him when it did need him, and it launched the installer while he was
//! away so the UAC prompt timed out. John: "build all of the rules into an install verb so you
//! stop screwing up and forgetting", "put in ai hints", "build that into your verbs now".
//!
//! THE ONE RULE: `needsHuman` may only ever name (1) a PASSWORD / 2FA, (2) the UAC prompt (secure
//! desktop, unreachable by any software), (3) clicks inside an ELEVATED window (UIPI blocks them
//! from a non-elevated bridge, and the bridge must never be elevated). Everything else the verb
//! does itself, and it LOOKS before it asks: every stage reads the real state first.
use crate::{ab, health, instance, license};
use serde_json::{json, Value};
/// Run a PowerShell snippet hidden; stdout trimmed, or None.
fn ps(script: &str) -> Option<String> {
let mut cmd = std::process::Command::new("powershell");
cmd.args(["-NoProfile", "-NonInteractive", "-Command", script]);
#[cfg(windows)]
{
use std::os::windows::process::CommandExt;
cmd.creation_flags(0x0800_0000); // CREATE_NO_WINDOW
}
let out = cmd.output().ok()?;
Some(String::from_utf8_lossy(&out.stdout).trim().to_string())
}
fn s(args: &Value, k: &str) -> Option<String> {
args.get(k).and_then(|v| v.as_str()).map(str::to_string).filter(|t| !t.is_empty())
}
/// The newest `AltiumDesignerSetup*.exe` in the user's Downloads, with its signature facts.
fn find_installer(explicit: Option<String>) -> Value {
let script = match explicit {
Some(p) => format!("$f = Get-Item -LiteralPath '{}' -ErrorAction SilentlyContinue", p.replace('\'', "''")),
None => "$f = Get-ChildItem \"$env:USERPROFILE\\Downloads\" -Filter 'AltiumDesignerSetup*.exe' -ErrorAction SilentlyContinue | Sort-Object LastWriteTime -Descending | Select-Object -First 1".to_string(),
} + "; if ($f) { $s = Get-AuthenticodeSignature $f.FullName; @{path=$f.FullName; mb=[math]::Round($f.Length/1MB,2); status=\"$($s.Status)\"; signer=\"$($s.SignerCertificate.Subject)\"; version=$f.VersionInfo.FileVersion} | ConvertTo-Json -Compress } else { '{}' }";
ps(&script).and_then(|t| serde_json::from_str::<Value>(&t).ok()).unwrap_or_else(|| json!({}))
}
/// Valid Authenticode signature by Altium. Anything else must never be run.
pub fn signature_ok(info: &Value) -> bool {
info.get("status").and_then(|v| v.as_str()) == Some("Valid")
&& info.get("signer").and_then(|v| v.as_str()).map(|t| t.contains("CN=Altium LLC")).unwrap_or(false)
}
/// Altium is installed when the bridge can find its X2.EXE.
fn altium_installed() -> bool {
crate::lifecycle::x2_exe_pub().is_some()
}
/// Toast the person at THIS machine and say truthfully whether it displayed.
fn toast(title: &str, body: &str) -> Value {
let r = ab::call("notify_user", json!({"title": title, "body": body, "level": "warning", "scenario": "reminder",
"buttons": [{"label": "On it"}], "reason": "altium_install needs the human for a step no software can do"}), &[], 20);
let shown = r.get("action").and_then(|a| a.as_str()) == Some("displayed");
json!({"displayed": shown, "error": r.get("error").cloned().unwrap_or(Value::Null),
"_hint": if shown { "Shown on THIS machine. If the user sits at a different machine they will not see it: also toast them where they are (notify_user with NO --target reaches the machine your container is attached to; `--target <mesh machine>` answered target_not_found on 2026-09-20) AND tell them in chat." }
else { "The toast did NOT display. Do not claim you notified the user: tell them in chat, and toast them with notify_user (no --target) from your side." }})
}
const HUMAN_ONLY: &str = "needsHuman may only ever name a password/2FA, the UAC prompt, or clicks inside an ELEVATED window. Release, Assign, Use License, Refresh, Sign in buttons, save prompts and browser navigation are ordinary clicks: do them yourself (UIA first, then desktop_click with a fresh shotId), and LOOK at a screenshot before you ask the human for anything.";
pub fn install(args: &Value) -> Value {
let stage = s(args, "stage").unwrap_or_else(|| "auto".into());
let installed = altium_installed();
let running = health::is_running();
let stage = if stage == "auto" {
if installed && running { "license" } else if installed { "launch_altium" } else if find_installer(s(args, "installerPath")).get("path").is_some() { "launch" } else { "preflight" }
} else { stage.as_str() }.to_string();
let mut out = match stage.as_str() {
"preflight" => {
let facts = ps("$c = Get-PSDrive C; $p = New-Object Security.Principal.WindowsPrincipal([Security.Principal.WindowsIdentity]::GetCurrent()); $k = Get-ItemProperty 'HKLM:\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Policies\\System'; @{freeGB=[math]::Round($c.Free/1GB); os=(Get-CimInstance Win32_OperatingSystem).Caption; bridgeElevated=$p.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator); uacOn=[bool]$k.EnableLUA; uacSecureDesktop=[bool]$k.PromptOnSecureDesktop; user=$env:USERNAME} | ConvertTo-Json -Compress")
.and_then(|t| serde_json::from_str::<Value>(&t).ok()).unwrap_or_else(|| json!({}));
let free = facts.get("freeGB").and_then(|v| v.as_f64()).unwrap_or(0.0);
json!({"success": free >= 25.0, "stage": "preflight", "installed": installed, "facts": facts,
"errorCode": if free >= 25.0 { Value::Null } else { json!("not_enough_disk") },
"_hint": "Altium needs ~10 GB installed and 15-25 GB with libraries and installer temp. bridgeElevated is normally false and must STAY false: it only tells you which windows you will be able to click.",
"_next": ["altium_install {stage:'get_installer'}"],
"pitfalls": ["There is NO public installer URL: downloads.altium.com holds only legacy archives. The current installer is behind an altium.com sign-in.",
"Never use the installer's unattended switches: Altium's silent mode takes the account PASSWORD on the command line."]})
}
"get_installer" => {
let info = find_installer(s(args, "installerPath"));
if info.get("path").is_none() {
json!({"success": false, "stage": "get_installer", "errorCode": "installer_not_found",
"error": "no AltiumDesignerSetup*.exe in this user's Downloads",
"_hint": "Get it with the user's altium.com session, on ANY machine, then copy the file here (send_files): 1) pup_open_window https://www.altium.com/products/downloads and pup_lower_os_window it at once. 2) pup_eval: signed in = zero `a.login-form` links and the buttons read 'Online Installer'. 3) If signed out: navigate to auth.altium.com, fill the account email, click Next, click the first `button.round-button` (Google). If Google asks for a password, THAT is the human step: raise the window (pup_raise_os_window), toast, and wait. 4) Signed in: click `a[data-id-in]` for the newest version INSIDE the page (pup_fetch_url sends no cookies and gets 401). 5) Verify the signature here with this stage again.",
"needsHuman": ["the Google/Altium PASSWORD, only if the browser session is not already signed in"],
"_next": ["altium_install {stage:'get_installer'}"]})
} else if !signature_ok(&info) {
json!({"success": false, "stage": "get_installer", "errorCode": "installer_signature_invalid", "installer": info,
"error": "the installer's Authenticode signature is not a Valid signature by Altium LLC: it will not be run",
"_hint": "Delete it and download again from altium.com. Never run an unsigned or differently signed installer."})
} else {
json!({"success": true, "stage": "get_installer", "installer": info,
"_hint": "Signature Valid, signer Altium LLC. Launch it only when the human is AT this machine: the UAC prompt auto-denies after about two minutes and the installer then exits.",
"_next": ["altium_install {stage:'launch'}"]})
}
}
"launch" => {
let info = find_installer(s(args, "installerPath"));
if !signature_ok(&info) {
return json!({"success": false, "stage": "launch", "errorCode": "installer_not_verified", "installer": info,
"_hint": "Run stage get_installer first; a missing or wrongly signed installer is never launched."});
}
if args.get("humanIsPresent").and_then(|b| b.as_bool()) != Some(true) {
return json!({"success": false, "stage": "launch", "errorCode": "human_presence_required", "installer": info,
"error": "launching raises a UAC prompt that only a person can answer and that auto-denies after ~2 minutes",
"_hint": "Ask the user to be at this machine's screen (RustDesk counts), THEN call again with humanIsPresent:true. Launching into an empty room wastes the prompt (it did, 2026-09-20).",
"needsHuman": ["be at this machine's screen before the launch"]});
}
let path = info.get("path").and_then(|p| p.as_str()).unwrap_or("").replace('\'', "''");
let _ = ps(&format!("Start-Process -FilePath '{path}'"));
let t = toast("Altium installer: I need you at this machine NOW",
"1) Yes on the UAC prompt (it auto-denies in ~2 min). 2) Next. 3) Accept the EULA (your agreement, not mine). 4) Sign in when the browser opens. 5) Keep the default features. 6) Install, then Finish. I will do everything after that myself.");
json!({"success": true, "stage": "launch", "installer": info, "toast": t,
"needsHuman": ["UAC: Yes (secure desktop: no software can click it)",
"the wizard: Next, accept the EULA, sign in in the browser, Install, Finish (the window is ELEVATED; UIPI blocks clicks from the non-elevated bridge)"],
"_hint": "The human's part ENDS at Finish. After that everything is yours, including licensing. Poll stage 'wait'. Tell the user in chat as well as by toast, and check `toast.displayed` before you say you notified anyone.",
"_next": ["altium_install {stage:'wait'}"],
"pitfalls": ["Screenshots of the elevated installer WORK (desktop_screenshot_screen); only clicks are blocked. Look at it to tell the user which page they are on.",
"The EULA is the user's agreement: say plainly that it is theirs to accept."]})
}
"wait" => {
let st = ps("@{consent=[bool](Get-Process consent -ErrorAction SilentlyContinue); installer=[bool](Get-Process | Where-Object { $_.Name -like 'AltiumDesignerSetup*' }); dir=(Test-Path 'C:\\Program Files\\Altium')} | ConvertTo-Json -Compress")
.and_then(|t| serde_json::from_str::<Value>(&t).ok()).unwrap_or_else(|| json!({}));
let consent = st.get("consent").and_then(|b| b.as_bool()).unwrap_or(false);
let inst = st.get("installer").and_then(|b| b.as_bool()).unwrap_or(false);
let hint = if altium_installed() { "Installed. Go straight to stage 'license': do NOT ask the user to sign in, the installer's browser sign-in normally carries over." }
else if consent { "The UAC prompt is UP and unanswered. It auto-denies in ~2 minutes: get the human to it NOW (toast + chat)." }
else if inst { "The installer is running (past UAC). desktop_screenshot_screen shows which page it is on." }
else { "No installer process and nothing installed: the UAC prompt was denied or timed out. Stage 'launch' again, with the human present." };
json!({"success": true, "stage": "wait", "installed": altium_installed(), "uacPromptUp": consent, "installerRunning": inst,
"_hint": hint, "_next": [if altium_installed() { "altium_install {stage:'license'}" } else { "altium_install {stage:'wait'}" }]})
}
"launch_altium" => json!({"success": true, "stage": "launch_altium", "installed": true,
"_hint": "Altium is installed but not running. altium_launch (cold start can exceed 2 minutes), then stage 'license'.",
"_next": ["altium_launch", "altium_install {stage:'license'}"]}),
"license" => {
if !running {
return json!({"success": false, "stage": "license", "errorCode": "altium_not_running", "_next": ["altium_launch"]});
}
let before = instance::licence_state();
if before.get("licensed").and_then(|b| b.as_bool()) == Some(true) {
return json!({"success": true, "stage": "license", "licensed": true, "licenceState": before.get("state"),
"_hint": "Already licensed. Done: altium_agent_start and drive.", "_next": ["altium_agent_start"]});
}
// DO IT, do not ask: Refresh, then Use License, then read the state again.
let refreshed = license::license(&json!({"action": "refresh"}));
std::thread::sleep(std::time::Duration::from_secs(4));
let mut used = Value::Null;
if instance::licence_state().get("licensed").and_then(|b| b.as_bool()) != Some(true) {
used = license::license(&json!({"action": "use_license"}));
std::thread::sleep(std::time::Duration::from_secs(5));
}
let after = instance::licence_state();
let ok = after.get("licensed").and_then(|b| b.as_bool()) == Some(true);
json!({"success": ok, "stage": "license", "licensed": ok, "licenceState": after.get("state"),
"did": {"refresh": refreshed.get("clicked"), "useLicense": used.get("clicked")},
"errorCode": if ok { Value::Null } else { json!("no_seat_available") },
"_hint": if ok { "Licensed by the bridge (Refresh / Use License), no human involved. altium_agent_start and drive." }
else { "Still Viewer. READ the page before asking anyone: altium_license {action:'show'} returns a screenshot. 'Sign Out' visible = the user IS signed in, no password needed. 'There are no seats available for this license' = the account's seat is held by ANOTHER running Altium (one On-Demand seat serves every Altium, Designer and Develop alike): altium_close it on that machine (cleanly, a kill strands the seat), then run this stage again. Release / Assign / Use License / Refresh are ordinary clicks: do them, do not hand them to the user." },
"needsHuman": if ok { json!([]) } else { json!(["a PASSWORD, only if the page shows 'Sign in' instead of 'Sign Out'"]) },
"_next": [if ok { "altium_agent_start" } else { "altium_license {action:'show'}" }]})
}
other => json!({"success": false, "errorCode": "unknown_stage", "error": format!("unknown stage '{other}'"),
"_hint": "stages, in order: preflight, get_installer, launch, wait, license (or omit stage: 'auto' picks the right one from the machine's state)"}),
};
out["rule"] = json!(HUMAN_ONLY);
out
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn only_a_valid_altium_signature_is_accepted() {
assert!(signature_ok(&json!({"status": "Valid", "signer": "CN=Altium LLC, O=Altium LLC, L=La Jolla, S=California, C=US"})));
assert!(!signature_ok(&json!({"status": "NotSigned", "signer": ""})));
assert!(!signature_ok(&json!({"status": "Valid", "signer": "CN=Somebody Else"})));
assert!(!signature_ok(&json!({"status": "HashMismatch", "signer": "CN=Altium LLC"})));
assert!(!signature_ok(&json!({})));
}
}