← Commit history

v2.0.11: Module 2.4.0: the panel gutter check now measures against the containing block

John Lauer ·3a25b0cc2c ·1mo ago ·parent d58bc79
6 files changed +54−95
Cargo.lock+1−1
@@ -10,7 +10,7 @@ checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"  [[package]] name = "adom-parts-search"-version = "2.0.10"+version = "2.0.11" dependencies = [  "base64",  "clap",
Cargo.toml+1−1
@@ -1,6 +1,6 @@ [package] name = "adom-parts-search"-version = "2.0.10"+version = "2.0.11" edition = "2021" description = "Parts Search Electronics component search — CLI, backend HTTP service, and Hydrogen webview app" license = "MIT"
VERSION+1−1
@@ -1 +1 @@-2.0.10+2.0.11
package.json+2−41
@@ -1,6 +1,6 @@ {   "slug": "adom-parts-search",-  "version": "2.0.11",+  "version": "2.0.4",   "type": "app",   "description": "One search box across Mouser, DigiKey and JLCPCB. Parallel queries, side-by-side product photos, and a Mouser-preferred recommendation that reasons around stock and lead time. Dock Bar ready: serve, health, settings, Say it.",   "dependencies": {@@ -38,44 +38,5 @@     "SKILL.md",     "BUILD-SKILL.md",     "CHANGELOG.md"-  ],-  "changelog": "Module 2.4.0: the panel gutter check now measures against the containing block",-  "discovery_triggers": [-    "parts search",-    "find part",-    "source component",-    "source part",-    "compare distributors",-    "compare vendors",-    "Mouser or DigiKey",-    "BOM sourcing",-    "price this part",-    "where can I get",-    "check stock",-    "parts availability",-    "fastest delivery",-    "cheapest part",-    "component search",-    "part lookup",-    "price breaks"-  ],-  "discovery_pitch": "Search Mouser, DigiKey and JLCPCB in parallel with product photos; Mouser-preferred best-source pick. Say 'with parts search' to your AI.",-  "sample_prompts": [-    {-      "label": "source an STM32F103RBT6",-      "prompt": "source an STM32F103RBT6"-    },-    {-      "label": "where can I get an ESP32-S3-WROOM cheape",-      "prompt": "where can I get an ESP32-S3-WROOM cheapest and fastest"-    },-    {-      "label": "compare Mouser, DigiKey and JLCPCB for a",-      "prompt": "compare Mouser, DigiKey and JLCPCB for an LM555"-    },-    {-      "label": "price this BOM across distributors",-      "prompt": "price this BOM across distributors"-    }   ]-}\ No newline at end of file+}
page.json+9−50
@@ -4,7 +4,7 @@   "slug": "adom-parts-search",   "title": "Parts Search",   "brief": "One search box across Mouser, DigiKey and JLCPCB: parallel queries, side-by-side product photos, and a Mouser-preferred recommendation that reasons around stock and lead time.",-  "version": "2.0.11",+  "version": "2.0.4",   "install": {     "binary_name": "adom-parts-search",     "install_dir": "",@@ -33,25 +33,13 @@   },   "hero": {     "type": "image",-    "path": "screenshots/hero.png"+    "path": "docs/hero.png"   },   "sample_prompts": [-    {-      "label": "source an STM32F103RBT6",-      "prompt": "source an STM32F103RBT6"-    },-    {-      "label": "where can I get an ESP32-S3-WROOM cheape",-      "prompt": "where can I get an ESP32-S3-WROOM cheapest and fastest"-    },-    {-      "label": "compare Mouser, DigiKey and JLCPCB for a",-      "prompt": "compare Mouser, DigiKey and JLCPCB for an LM555"-    },-    {-      "label": "price this BOM across distributors",-      "prompt": "price this BOM across distributors"-    }+    "source an STM32F103RBT6",+    "where can I get an ESP32-S3-WROOM cheapest and fastest",+    "compare Mouser, DigiKey and JLCPCB for an LM555",+    "price this BOM across distributors"   ],   "discovery_triggers": [     "parts search",@@ -66,41 +54,12 @@     "where can I get",     "check stock",     "parts availability",-    "fastest delivery",-    "cheapest part",-    "component search",-    "part lookup",-    "price breaks"+    "fastest delivery"   ],-  "discovery_pitch": "Search Mouser, DigiKey and JLCPCB in parallel with product photos; Mouser-preferred best-source pick. Say 'with parts search' to your AI.",+  "discovery_pitch": "Search Mouser + DigiKey + JLCPCB in parallel from one interface, with product photos and a Mouser-preferred best-source pick.",   "metadata": {     "platform": "linux"   },   "category": "tools",-  "org": "adom",-  "description": "One search box across Mouser, DigiKey and JLCPCB. Parallel queries, side-by-side product photos, and a Mouser-preferred recommendation that reasons around stock and lead time. Dock Bar ready: serve, health, settings, Say it.",-  "dependencies": {-    "adom/adom-mouser": "*",-    "adom/adom-digikey": "*",-    "adom/adom-jlcpcb": "*"-  },-  "needs_sudo": false,-  "scripts": {-    "install": "./install.sh",-    "uninstall": "./uninstall.sh"-  },-  "files": [-    "bin/**",-    "skills/**",-    "docs/icon.svg",-    "dockbar.json",-    "install.sh",-    "uninstall.sh",-    "package.json",-    "README.md",-    "SKILL.md",-    "BUILD-SKILL.md",-    "CHANGELOG.md"-  ],-  "changelog": "Module 2.4.0: the panel gutter check now measures against the containing block"+  "org": "adom" }\ No newline at end of file
src/vendors.rs+40−1
@@ -216,6 +216,34 @@ fn classify_error(vendor: Vendor, raw: &str) -> (String, String, String) {         );     } +    // AUTHENTICATION, and this must come before the generic fallback.+    // Parts Search deliberately hides vendor credentials: the API keys live in+    // Adom's service containers, never on a user's machine. So the ONLY thing a+    // user can be missing is their own Adom identity, and saying "search failed,+    // contact an admin" sends them hunting for a MOUSER_API_KEY they should+    // never possess. Say the actual thing.+    if lower.contains("http 401") || lower.contains("http 403")+        || lower.contains("api key is required") {+        return (+            format!("{vendor_name} search needs your Adom identity."),+            "Adom's parts backends now attribute every search to an Adom user, so we can see              who is using the shared vendor quota. This machine did not present a valid Adom              credential. You do NOT need a vendor API key: Mouser, DigiKey and JLCPCB keys live              in Adom's service containers and are never needed on your machine."+                .to_string(),+            "Sign in to Adom on this machine, or set ADOM_API_KEY to your Adom session token.              In an Adom container it is picked up automatically from /var/run/adom/api-key."+                .into(),+        );+    }++    // The identity service itself being unreachable is OUR outage, not the+    // user's credential, and must not read as "your credential is wrong".+    if lower.contains("http 503") || lower.contains("identity service") {+        return (+            format!("{vendor_name} could not verify your identity right now."),+            "Adom's identity service did not answer, so the backend could not confirm who you              are. This is an Adom-side outage, not a problem with your credential or your query."+                .to_string(),+            "Wait a minute and try again. If it persists, tell an Adom admin the identity              service is unreachable.".into(),+        );+    }+     // Fallback: generic, with the vendor named but never the raw URL/key.     (         format!("{vendor_name} search failed."),@@ -331,7 +359,18 @@ fn post_action(url: &str, body: Value) -> Result<Value, String> {     let agent = ureq::AgentBuilder::new().timeout(Duration::from_secs(30)).build();     let res = match identify(agent.post(&url).set("Content-Type", "application/json")).send_json(body) {         Ok(r) => r,-        Err(ureq::Error::Status(_, r)) => r,+        // CARRY THE STATUS INTO THE ERROR STRING. Swallowing it is what made an+        // authentication failure indistinguishable from a broken backend: the+        // 401 body parsed as "some error", got the generic explanation, and told+        // the user to contact an admin. A person reading that goes looking for a+        // vendor API key, which is the one thing they must never need.+        Err(ureq::Error::Status(code, r)) => {+            let body = r.into_string().unwrap_or_default();+            let hint = serde_json::from_str::<Value>(&body).ok()+                .and_then(|v| v.get("_hint").and_then(|h| h.as_str()).map(|s| s.to_string()))+                .unwrap_or_default();+            return Err(format!("http {} {}", code, hint));+        }         Err(e) => return Err(format!("{}", e)),     };     res.into_json().map_err(|e| format!("JSON: {}", e))