app
Adom Parts Search
Public Made by Adomby adom
One search box across Mouser + DigiKey + JLCPCB. Parallel queries, side-by-side product photos, and a Mouser-preferred recommendation (40-min drone delivery to Fort Worth) that reasons around stock and lead time.
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495
//! `adom-parts-search install` — deploy SKILL.md + BUILD-SKILL.md + bash completions.
//!
//! Per adom-cli-design: skills are NOT embedded in the binary — they
//! live as independent assets on the wiki and are fetched at install
//! time. Prose changes often; a binary rebuild + release shouldn't be
//! required for a typo fix. Only fall back to the bundled copy when the
//! wiki is unreachable (network outage) so fresh installs still work
//! offline or during migrations.
use crate::{ok, warn};
use clap::CommandFactory;
use clap_complete::Shell;
use std::fs;
use std::io::Write;
use std::path::PathBuf;
use std::time::Duration;
// SAFETY NET only — do not rely on these for day-to-day release
// propagation. The wiki-hosted copy at
// https://wiki-ufypy5dpx93o.adom.cloud/static/apps/adom-parts-search/SKILL.md
// is authoritative.
const FALLBACK_SKILL_MD: &str = include_str!("../SKILL.md");
const FALLBACK_BUILD_SKILL_MD: &str = include_str!("../BUILD-SKILL.md");
const WIKI_SKILL_URL: &str = "https://wiki-ufypy5dpx93o.adom.cloud/static/apps/adom-parts-search/SKILL.md";
const WIKI_BUILD_SKILL_URL: &str = "https://wiki-ufypy5dpx93o.adom.cloud/static/apps/adom-parts-search/BUILD-SKILL.md";
fn fetch_or_fallback(url: &str, fallback: &'static str, label: &str) -> String {
let agent = ureq::AgentBuilder::new().timeout(Duration::from_secs(5)).build();
match agent.get(url).call() {
Ok(r) => match r.into_string() {
Ok(body) if body.len() >= 200 => body,
_ => {
warn(format!("wiki returned short/unreadable {label} — using bundled fallback"));
fallback.to_string()
}
},
Err(_) => {
warn(format!("wiki unreachable, installing bundled {label} (may be stale — re-run `adom-parts-search install` once network is up)"));
fallback.to_string()
}
}
}
pub fn install() -> Result<(), String> {
let home = std::env::var("HOME").map_err(|_| "HOME env var not set".to_string())?;
let skills_root = PathBuf::from(&home).join(".claude").join("skills");
// 1a. User-facing skill (fetched from wiki; bundled copy is fallback)
let skill_md = fetch_or_fallback(WIKI_SKILL_URL, FALLBACK_SKILL_MD, "SKILL.md");
let skill_dir = skills_root.join("adom-parts-search");
fs::create_dir_all(&skill_dir).map_err(|e| format!("create {}: {}", skill_dir.display(), e))?;
let skill_path = skill_dir.join("SKILL.md");
fs::write(&skill_path, &skill_md).map_err(|e| format!("write {}: {}", skill_path.display(), e))?;
ok(format!("installed skill → {} ({} bytes)", skill_path.display(), skill_md.len()));
// 1b. Build skill (per-app release workflow)
let build_skill_md = fetch_or_fallback(WIKI_BUILD_SKILL_URL, FALLBACK_BUILD_SKILL_MD, "BUILD-SKILL.md");
let build_skill_dir = skills_root.join("adom-parts-search-build");
fs::create_dir_all(&build_skill_dir)
.map_err(|e| format!("create {}: {}", build_skill_dir.display(), e))?;
let build_skill_path = build_skill_dir.join("SKILL.md");
fs::write(&build_skill_path, &build_skill_md)
.map_err(|e| format!("write {}: {}", build_skill_path.display(), e))?;
ok(format!("installed build skill → {} ({} bytes)", build_skill_path.display(), build_skill_md.len()));
// 2. Bash completions
let comp_dir = PathBuf::from(&home)
.join(".local")
.join("share")
.join("bash-completion")
.join("completions");
fs::create_dir_all(&comp_dir).map_err(|e| format!("create {}: {}", comp_dir.display(), e))?;
let comp_path = comp_dir.join("adom-parts-search");
let mut cmd = crate::Cli::command();
let mut buf: Vec<u8> = Vec::new();
clap_complete::generate(Shell::Bash, &mut cmd, "adom-parts-search", &mut buf);
fs::write(&comp_path, &buf).map_err(|e| format!("write {}: {}", comp_path.display(), e))?;
ok(format!("installed completions → {}", comp_path.display()));
// 3. Append eval to .bashrc once
let bashrc = PathBuf::from(&home).join(".bashrc");
let current = fs::read_to_string(&bashrc).unwrap_or_default();
if !current.contains("# adom-parts-search completions") {
match fs::OpenOptions::new().append(true).open(&bashrc) {
Ok(mut f) => {
let _ = f.write_all(b"\n# adom-parts-search completions\neval \"$(adom-parts-search completions bash 2>/dev/null)\"\n");
ok("appended completion source line to ~/.bashrc (open a new shell for it to take effect)");
}
Err(e) => warn(format!(".bashrc: {}", e)),
}
}
Ok(())
}