app
Mouser Electronics Search
Public Made by Adomby adom
Mouser component search in your workspace — stock, quantity price breaks, datasheets and lifecycle. CLI verbs, a Hydrogen app, and a shared backend service.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131
//! One-shot CLI verbs: `adom-mouser search|part|health`.
//!
//! Resolution order for the data source:
//! 1. If `--api` / `MOUSER_API` is set → talk to that backend via POST /
//! 2. If http://127.0.0.1:8775 is reachable → use it as the backend
//! 3. Else fall back to a direct Mouser API call (requires MOUSER_API_KEY)
use crate::mouser::{self, SearchResult};
use crate::{ok, warn, HealthArgs, PartArgs, SearchArgs};
use serde_json::{json, Value};
use std::time::Duration;
fn resolve_backend(explicit: Option<String>) -> Option<String> {
if let Some(api) = explicit.filter(|s| !s.is_empty()) {
return Some(api);
}
let local = "http://127.0.0.1:8775".to_string();
let agent = ureq::AgentBuilder::new()
.timeout(Duration::from_secs(2))
.build();
match agent.get(&format!("{}/health", local)).call() {
Ok(_) => Some(local),
Err(_) => None,
}
}
fn backend_post(api: &str, body: Value) -> Result<Value, String> {
let url = if api.ends_with('/') { api.to_string() } else { format!("{}/", api) };
let agent = ureq::AgentBuilder::new()
.timeout(Duration::from_secs(30))
.build();
let res = match agent.post(&url).set("Content-Type", "application/json").send_json(body) {
Ok(r) => r,
Err(ureq::Error::Status(_code, r)) => r,
Err(e) => return Err(format!("backend POST failed: {}", e)),
};
res.into_json().map_err(|e| format!("backend JSON parse failed: {}", e))
}
fn emit(result: SearchResult) {
// Always print clean JSON on stdout (so the AI can pipe `| jq`), and
// emit an OK: line on a separate line so the AI knows success. The
// OK: line goes to stdout before the JSON so it doesn't corrupt the
// JSON if somebody does `| jq`. Actually — emit OK on stderr so the
// stdout stream is pure JSON.
if crate::stderr_is_tty() {
eprintln!(
"{}{}OK:{} {} components (query: {})",
crate::GREEN,
crate::BOLD,
crate::RESET,
result.components.len(),
result.query
);
} else {
eprintln!("OK: {} components (query: {})", result.components.len(), result.query);
}
let v = serde_json::to_value(&result).unwrap_or_else(|_| json!({}));
println!("{}", serde_json::to_string_pretty(&v).unwrap_or_default());
}
pub fn run_search(args: SearchArgs) -> Result<(), String> {
let backend = resolve_backend(args.api);
let result = if let Some(api) = backend.as_ref() {
let v = backend_post(
api,
json!({
"action": "search",
"keyword": args.keyword,
"limit": args.limit,
"inStockOnly": args.in_stock_only,
}),
)?;
if let Some(e) = v.get("error").and_then(|v| v.as_str()) {
return Err(e.into());
}
serde_json::from_value(v).map_err(|e| format!("response parse: {}", e))?
} else {
warn("No backend at http://127.0.0.1:8775 and no --api / MOUSER_API set; calling Mouser API directly.");
let key = std::env::var("MOUSER_API_KEY").unwrap_or_default();
mouser::search(&key, &args.keyword, args.limit, args.in_stock_only)?
};
emit(result);
Ok(())
}
pub fn run_part(args: PartArgs) -> Result<(), String> {
let backend = resolve_backend(args.api);
let result = if let Some(api) = backend.as_ref() {
let v = backend_post(api, json!({"action": "part", "partNumber": args.part_number}))?;
if let Some(e) = v.get("error").and_then(|v| v.as_str()) {
return Err(e.into());
}
serde_json::from_value(v).map_err(|e| format!("response parse: {}", e))?
} else {
warn("No backend at http://127.0.0.1:8775 and no --api / MOUSER_API set; calling Mouser API directly.");
let key = std::env::var("MOUSER_API_KEY").unwrap_or_default();
mouser::part(&key, &args.part_number)?
};
emit(result);
Ok(())
}
pub fn run_health(args: HealthArgs) -> Result<(), String> {
let backend = resolve_backend(args.api);
if let Some(api) = backend.as_ref() {
let url = if api.ends_with('/') {
format!("{}health", api)
} else {
format!("{}/health", api)
};
let agent = ureq::AgentBuilder::new()
.timeout(Duration::from_secs(10))
.build();
match agent.get(&url).call() {
Ok(res) => {
let v: Value = res.into_json().map_err(|e| format!("JSON: {}", e))?;
ok(format!("backend {} reachable", api));
println!("{}", serde_json::to_string_pretty(&v).unwrap_or_default());
Ok(())
}
Err(e) => Err(format!("backend at {} unreachable: {}", api, e)),
}
} else {
warn("No backend at http://127.0.0.1:8775 and no --api / MOUSER_API set; pinging Mouser directly.");
let key = std::env::var("MOUSER_API_KEY").unwrap_or_default();
let n = mouser::ping(&key)?;
ok(format!("Mouser API reachable (test query returned {} results)", n));
Ok(())
}
}