app
Parts Search
Public Made by Adomby adom
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.
← Commit history
v2.0.11: Module 2.4.0: the panel gutter check now measures against the containing block
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"- } ]-}+}
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" }
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))