← Commit history

Delete rust/src/verbs.rs

John Lauer ·5cfcd487b5 ·29d ago ·parent 07217df
1 file changed −211
rust/src/verbs.rsdeleted−211
@@ -1,211 +0,0 @@-//! Verb handlers for phase 1 (headless). Window, routing and demo verbs come in later phases.--use std::path::PathBuf;-use std::time::Duration;--use serde_json::{json, Value};--use crate::catalog;-use crate::cli;-use crate::detect::{self, KicadInfo};-use crate::Caller;--pub struct State {-    info: Option<(std::time::Instant, KicadInfo)>,-}--impl State {-    pub fn new() -> Self {-        State { info: None }-    }-    /// Detection is cached for 60 s; a verb that needs fresh truth calls refresh().-    pub fn kicad_info(&mut self) -> KicadInfo {-        if let Some((t, ref i)) = self.info {-            if t.elapsed() < Duration::from_secs(60) {-                return i.clone();-            }-        }-        self.refresh()-    }-    pub fn refresh(&mut self) -> KicadInfo {-        let i = detect::detect();-        self.info = Some((std::time::Instant::now(), i.clone()));-        i-    }-}--fn fail(code: &str, msg: impl Into<String>, hint: impl Into<String>) -> Value {-    json!({"success": false, "errorCode": code, "error": msg.into(), "_hint": hint.into()})-}--fn arg_str<'a>(args: &'a Value, key: &str) -> Option<&'a str> {-    args.get(key).and_then(Value::as_str).filter(|s| !s.trim().is_empty())-}--fn alternatives() -> Value {-    json!([-        {"backend": "service", "what": "The shared service-kicad container runs DRC, ERC, exports, renders and library lookups with no desktop KiCad.", "hint": "Load the service-kicad skill; pass backend:\"service\" to the headless verbs."},-        {"backend": "container", "what": "Install kicad-cli into your own container.", "hint": "The service-kicad skill has the install; then the headless verbs run locally."},-        {"backend": "target", "what": "Run KiCad on another box (a second laptop, a conference room machine, a Grok Computer session) and address it by name through Adom Bridge.", "hint": "adom-bridge targets lists the boxes; add --target <name> to every call."}-    ])-}--pub fn dispatch(state: &mut State, command: &str, args: &Value, _caller: &Caller) -> Value {-    let Some(verb) = catalog::find(command) else {-        let names: Vec<&str> = catalog::VERBS.iter().map(|v| v.name).collect();-        return fail("unknown_verb", format!("Unknown command: {command}"), format!("Valid commands: {}", names.join(", ")));-    };-    let mut out = match command {-        "kicad_describe" => catalog::describe(crate::VERSION),-        "kicad_status" => status(state),-        "kicad_readiness" => readiness(state),-        "kicad_list_versions" => list_versions(state),-        "kicad_diagnostics" => diagnostics(state),-        "kicad_run_drc" => run_check(state, args, true),-        "kicad_run_erc" => run_check(state, args, false),-        "kicad_uninstall" => uninstall(),-        _ => fail("not_implemented", format!("{command} is catalogued but not implemented in this phase"), "Use the Python bridge for this verb until the phase that ports it lands."),-    };-    if let Some(o) = out.as_object_mut() {-        o.entry("mechanism").or_insert(json!(verb.mechanism.as_str()));-    }-    out-}--fn status(state: &mut State) -> Value {-    let info = state.kicad_info();-    json!({-        "success": true,-        "bridgeVersion": crate::VERSION,-        "runtime": "native",-        "installed": info.installed,-        "kicadVersion": info.version,-        "install": info.primary(),-        "_hint": if info.installed { "KiCad found. Headless verbs (run_drc, run_erc) are ready." } else { "KiCad not found here. kicad_readiness lists the alternatives." },-    })-}--fn readiness(state: &mut State) -> Value {-    let info = state.refresh();-    let mut v = json!({-        "success": true,-        "installed": info.installed,-        "ready": info.installed,-        "kicadVersion": info.version,-        "installs": info.installs,-        "configDir": info.primary().and_then(|p| detect::config_dir(&p.version)).map(|p| p.to_string_lossy().replace('\\', "/")),-        "runtime": {"kind": "native", "provisioned": false},-    });-    if !info.installed {-        v["errorCode"] = json!("not_installed");-        v["alternatives"] = alternatives();-        v["_hint"] = json!("No KiCad on this box. Pick one of `alternatives`, or call kicad_upgrade to install KiCad silently (asks the user first).");-    } else {-        v["_hint"] = json!("Ready. Headless verbs need nothing else; window verbs are served by the Python bridge until phase 3.");-    }-    v-}--fn list_versions(state: &mut State) -> Value {-    let info = state.refresh();-    json!({"success": true, "count": info.installs.len(), "installs": info.installs, "primary": info.primary()})-}--fn diagnostics(state: &mut State) -> Value {-    let info = state.kicad_info();-    let keys = ["USERPROFILE", "APPDATA", "LOCALAPPDATA", "ProgramFiles", "ProgramFiles(x86)", "ADOM_BIND_HOST", "ADOM_DIRECT_API_URL", "HOME", "PATH"];-    let env: serde_json::Map<String, Value> = keys-        .iter()-        .map(|k| (k.to_string(), json!(std::env::var(k).ok())))-        .collect();-    json!({-        "success": true,-        "bridgeVersion": crate::VERSION,-        "runtime": "native",-        "exe": std::env::current_exe().ok().map(|p| p.to_string_lossy().replace('\\', "/")),-        "cwd": std::env::current_dir().ok().map(|p| p.to_string_lossy().replace('\\', "/")),-        "os": std::env::consts::OS,-        "arch": std::env::consts::ARCH,-        "env": env,-        "kicad": info,-        "configDir": info.primary().and_then(|p| detect::config_dir(&p.version)).map(|p| p.to_string_lossy().replace('\\', "/")),-    })-}--fn run_check(state: &mut State, args: &Value, drc: bool) -> Value {-    let key = if drc { "boardPath" } else { "schematicPath" };-    let Some(path) = arg_str(args, key) else {-        return fail("missing_arg", format!("{key} is required"), format!("Pass the absolute path of the {} file.", if drc { ".kicad_pcb" } else { ".kicad_sch" }));-    };-    let path = PathBuf::from(path);-    if !path.is_file() {-        return fail("file_not_found", format!("{} does not exist", path.display()), "Check the path; the bridge runs on the desktop, so use the desktop's path, not the container's.");-    }-    let backend = arg_str(args, "backend").unwrap_or("local");-    if backend != "local" {-        return fail("backend_not_implemented", format!("backend {backend} lands with phase 1b"), "Use backend local for now, or run service-kicad from the container directly.");-    }-    let info = state.kicad_info();-    let Some(inst) = info.primary() else {-        let mut v = fail("not_installed", "KiCad is not installed on this box", "See alternatives.");-        v["alternatives"] = alternatives();-        return v;-    };-    let cli_path = PathBuf::from(&inst.kicad_cli);-    let report = std::env::temp_dir().join(format!("adom-kicad-{}-{}.json", if drc { "drc" } else { "erc" }, std::process::id()));-    let report_s = report.to_string_lossy().to_string();-    let path_s = path.to_string_lossy().to_string();-    let mut argv: Vec<&str> = if drc {-        vec!["pcb", "drc", "--format", "json", "--severity-all", "--all-track-errors"]-    } else {-        vec!["sch", "erc", "--format", "json", "--severity-all"]-    };-    if drc && args.get("refillZones").and_then(Value::as_bool).unwrap_or(false) {-        argv.push("--refill-zones");-    }-    argv.extend(["--output", report_s.as_str(), path_s.as_str()]);-    let r = cli::run(&cli_path, &argv, Duration::from_secs(280));-    let parsed = std::fs::read_to_string(&report).ok().and_then(|s| serde_json::from_str::<Value>(&s).ok());-    let _ = std::fs::remove_file(&report);-    match parsed {-        Some(rep) => {-            let summary = cli::summarize_report(&rep);-            json!({-                "success": true,-                "check": if drc { "drc" } else { "erc" },-                "path": path_s,-                "summary": summary,-                "cliExitCode": r.exit_code,-                "cliElapsedMs": r.elapsed_ms,-                "cliVersion": inst.cli_version,-                "report": rep,-                "_hint": if drc { "summary.clean is true only with zero errors and zero unconnected items; warnings are listed but do not fail the board." } else { "summary.clean means zero errors; ERC warnings are listed." },-            })-        }-        None => {-            let mut v = fail(-                if r.timed_out { "cli_timeout" } else { "cli_failed" },-                format!("kicad-cli exit {}: {}", r.exit_code, r.stderr.trim()),-                "kicad-cli wrote no report. The stderr above usually names the reason (file version, missing library, bad path).",-            );-            v["stdout"] = json!(r.stdout);-            v["cliElapsedMs"] = json!(r.elapsed_ms);-            v-        }-    }-}--fn uninstall() -> Value {-    let mut cleaned: Vec<String> = Vec::new();-    for name in ["adom-kicad-drc", "adom-kicad-erc"] {-        if let Ok(rd) = std::fs::read_dir(std::env::temp_dir()) {-            for e in rd.flatten() {-                let n = e.file_name().to_string_lossy().to_string();-                if n.starts_with(name) && n.ends_with(".json") && std::fs::remove_file(e.path()).is_ok() {-                    cleaned.push(n);-                }-            }-        }-    }-    json!({"success": true, "cleaned": cleaned, "_hint": "Only bridge scratch files are removed. KiCad, its settings and user libraries are untouched."})-}