app
DigiKey Electronics Search
Public Made by Adomby adom
DigiKey component search in your workspace — millions of parts with real-time stock, quantity pricing and datasheets. CLI verbs, a Hydrogen app, and a shared backend service.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205
//! adom-digikey: DigiKey Electronics component search.
//!
//! One binary, three modes:
//! - `adom-digikey serve` — backend HTTP proxy (port 8777). Replaces
//! the old gallia/services/digikey/server.js.
//! - `adom-digikey search|part|health` — one-shot CLI verbs.
//! - `adom-digikey app` — opens a Hydrogen webview tab with a
//! searchable UI. Talks HTTP to a local or
//! remote `serve` instance.
mod app;
mod cli;
mod install;
mod digikey;
mod service;
use clap::{CommandFactory, Parser, Subcommand};
use std::io::IsTerminal;
use std::process::ExitCode;
use std::sync::OnceLock;
// ANSI colour constants. Every user-facing emitter (ok/err/hint/warn/note)
// is gated on isatty so piped output stays ANSI-free. See adom-cli-design.
pub const GREEN: &str = "\x1b[32m";
pub const RED: &str = "\x1b[31m";
pub const YELLOW: &str = "\x1b[33m";
pub const CYAN: &str = "\x1b[36m";
pub const BOLD: &str = "\x1b[1m";
pub const DIM: &str = "\x1b[2m";
pub const RESET: &str = "\x1b[0m";
static STDOUT_IS_TTY: OnceLock<bool> = OnceLock::new();
static STDERR_IS_TTY: OnceLock<bool> = OnceLock::new();
pub fn stdout_is_tty() -> bool { *STDOUT_IS_TTY.get_or_init(|| std::io::stdout().is_terminal()) }
pub fn stderr_is_tty() -> bool { *STDERR_IS_TTY.get_or_init(|| std::io::stderr().is_terminal()) }
pub fn strip_ansi(s: &str) -> String {
let mut out = String::with_capacity(s.len());
let mut chars = s.chars().peekable();
while let Some(c) = chars.next() {
if c == '\x1b' && chars.peek() == Some(&'[') {
chars.next();
for c2 in chars.by_ref() {
if c2.is_ascii_alphabetic() { break; }
}
continue;
}
out.push(c);
}
out
}
pub fn ok(msg: impl std::fmt::Display) {
if stdout_is_tty() {
println!("{GREEN}{BOLD}OK:{RESET} {msg}");
} else {
println!("OK: {}", strip_ansi(&msg.to_string()));
}
}
pub fn err(msg: impl std::fmt::Display) {
if stderr_is_tty() {
eprintln!("{RED}{BOLD}ERROR:{RESET} {msg}");
} else {
eprintln!("ERROR: {}", strip_ansi(&msg.to_string()));
}
}
pub fn hint(msg: impl std::fmt::Display) {
if stderr_is_tty() {
eprintln!("{DIM}Hint:{RESET} {msg}");
} else {
eprintln!("Hint: {}", strip_ansi(&msg.to_string()));
}
}
pub fn warn(msg: impl std::fmt::Display) {
if stderr_is_tty() {
eprintln!("{YELLOW}{BOLD}WARN:{RESET} {msg}");
} else {
eprintln!("WARN: {}", strip_ansi(&msg.to_string()));
}
}
#[macro_export]
macro_rules! note {
($($arg:tt)*) => {{
let __s = format!($($arg)*);
if $crate::stdout_is_tty() {
println!("{}", __s);
} else {
println!("{}", $crate::strip_ansi(&__s));
}
}};
}
#[derive(Parser)]
#[command(name = "adom-digikey", version, about = "DigiKey Electronics search: CLI, backend HTTP service, and webview app")]
pub struct Cli {
#[command(subcommand)]
cmd: Cmd,
}
#[derive(Subcommand)]
enum Cmd {
/// Run the backend HTTP proxy on port 8777 (replacement for the old
/// gallia/services/digikey/server.js). Keeps the API key server-side,
/// caches responses in-memory, exposes a JSON API used by CLI + app.
Serve(ServeArgs),
/// One-shot search: prints JSON results for a keyword or MPN.
Search(SearchArgs),
/// One-shot part lookup: prints JSON for a specific part.
Part(PartArgs),
/// Check backend/service health (reachability + API-key + test call).
Health(HealthArgs),
/// Open the DigiKey search webview as a Hydrogen tab. Runs a local
/// HTTP server, talks to a backend for real API calls.
App(AppArgs),
/// Install SKILL.md + bash completions to ~/.claude and ~/.local.
Install,
/// Print bash completion script (used by `install`).
Completions { shell: clap_complete::Shell },
}
#[derive(clap::Args)]
pub struct ServeArgs {
/// Port to listen on.
#[arg(long, env = "DIGIKEY_PORT", default_value_t = 8777)]
pub port: u16,
}
#[derive(clap::Args)]
pub struct SearchArgs {
/// Keyword, manufacturer part number, or DigiKey part number.
pub keyword: String,
/// Max results (1-50).
#[arg(long, default_value_t = 10)]
pub limit: u32,
/// Only parts currently in stock.
#[arg(long)]
pub in_stock_only: bool,
/// Backend URL (defaults to $DIGIKEY_API or http://127.0.0.1:8777).
#[arg(long, env = "DIGIKEY_API")]
pub api: Option<String>,
}
#[derive(clap::Args)]
pub struct PartArgs {
/// DigiKey PN (296-STM32F103RBT6-ND) or MPN (STM32F103RBT6).
pub part_number: String,
#[arg(long, env = "DIGIKEY_API")]
pub api: Option<String>,
}
#[derive(clap::Args)]
pub struct HealthArgs {
#[arg(long, env = "DIGIKEY_API")]
pub api: Option<String>,
}
#[derive(clap::Args)]
pub struct AppArgs {
/// Port the local app server listens on.
#[arg(long, default_value_t = 8891)]
pub port: u16,
/// Backend URL for DigiKey API calls (defaults to $DIGIKEY_API or http://127.0.0.1:8777).
#[arg(long, env = "DIGIKEY_API")]
pub api: Option<String>,
/// Hydrogen tab name.
#[arg(long, default_value = "DigiKey Search")]
pub tab_name: String,
/// Skip opening the Hydrogen tab (useful for local browser testing).
#[arg(long)]
pub no_tab: bool,
/// Enable `/eval` hot-patch channel for development.
#[arg(long, env = "ADOM_MOUSER_DEV")]
pub dev: bool,
}
fn main() -> ExitCode {
let args = Cli::parse();
let result = match args.cmd {
Cmd::Serve(a) => service::run_serve(a),
Cmd::Search(a) => cli::run_search(a),
Cmd::Part(a) => cli::run_part(a),
Cmd::Health(a) => cli::run_health(a),
Cmd::App(a) => app::run(a),
Cmd::Install => install::install(),
Cmd::Completions { shell } => {
let mut cmd = Cli::command();
clap_complete::generate(shell, &mut cmd, "adom-digikey", &mut std::io::stdout());
Ok(())
}
};
match result {
Ok(()) => ExitCode::SUCCESS,
Err(e) => {
err(&e);
ExitCode::FAILURE
}
}
}