use erga_app::{pool, pools, store};
const MARK: &str = "\x1b[38;5;121m";
const DIM: &str = "\x1b[38;5;244m";
const WARM: &str = "\x1b[38;5;215m";
const OFF: &str = "\x1b[0m";
pub fn help() {
let v = env!("CARGO_PKG_VERSION");
println!(
"\n{MARK} โฌก erga{OFF} {DIM}v{v} ยท one-button ERGO miner for Apple Silicon{OFF}\n"
);
for (cmd, what) in [
("erg", "open the window"),
("erg mine [host] [port] [address]", "mine here, no window"),
("erg mine --intensity max|eco|min", "how hard to push"),
("erg status", "what the pool owes you"),
("erg link", "put the command on your PATH"),
("erg difftest", "prove the GPU kernel against the reference"),
("erg buildbench [height]", "time one epoch-table build"),
("erg help", "this"),
] {
println!(" {MARK}{cmd:<34}{OFF}{DIM}{what}{OFF}");
}
println!("\n {DIM}`erga` and `erg` are the same command.{OFF}");
println!(
"\n {DIM}mine, with nothing after it, uses the pool you picked in the window\n \
and the wallet it generated. every share is re-verified on the CPU\n \
reference before it is sent.{OFF}\n"
);
}
fn row(key: &str, val: &str) {
println!(" {DIM}{key:<22}{OFF}{val}");
}
pub fn status() {
let idx = pools::load_choice();
let pl = pools::get(idx);
let addr = match erga_app::payout_address() {
Ok(a) => a,
Err(e) => {
eprintln!("no wallet: {e}");
std::process::exit(1);
}
};
println!("\n{MARK} โฌก erga{OFF} {DIM}{}{OFF}\n", pl.label);
let p = pool::snapshot(&addr, idx);
row("address", &addr);
if let Some(e) = &p.error {
println!(" {WARM}{e}{OFF}\n");
return;
}
let earned = p.balance_erg + p.pending_erg;
let pct = (earned / p.threshold_erg * 100.0).min(100.0);
row("credited", &format!("{:.5} ERG", p.balance_erg.max(0.0)));
row("maturing", &format!("{:.5} ERG", p.pending_erg.max(0.0)));
if p.paid_erg > 0.0 {
row("paid out", &format!("{:.5} ERG", p.paid_erg));
}
row(
"toward payout",
&format!("{pct:.1}% of {:.1} ERG", p.threshold_erg),
);
row("pool sees", &format!("{:.0} MH/s (24h)", p.hashrate_24h_mhs));
if p.difficulty > 0.0 && p.hashrate_24h_mhs > 0.01 {
let net = p.difficulty / pool::BLOCK_TIME_S;
let per_day = p.hashrate_24h_mhs * 1e6 / net * (86_400.0 / pool::BLOCK_TIME_S)
* pool::BLOCK_REWARD_ERG;
let usd = if p.price_usd > 0.0 {
format!(" ยท ${:.2}", per_day * 30.0 * p.price_usd)
} else {
String::new()
};
row("a month at this pace", &format!("โ {:.2} ERG{usd}", per_day * 30.0));
}
println!();
}
pub fn link() {
let Ok(exe) = std::env::current_exe() else {
eprintln!("cannot find my own path");
std::process::exit(1);
};
match place_link(&exe) {
Some(l) => {
println!("\n {MARK}linked{OFF} {} {DIM}(and erg){OFF}\n", l.path.display());
if !l.on_path {
let dir = l.path.parent().unwrap_or(&l.path).display();
println!(
" {DIM}that directory is not on your PATH yet:{OFF}\n \
{DIM} export PATH=\"{dir}:$PATH\"{OFF}\n"
);
}
}
None => {
eprintln!(
"\n could not write to /usr/local/bin or ~/.local/bin.\n \
link it yourself:\n ln -sf {} /usr/local/bin/erga\n",
exe.display()
);
std::process::exit(1);
}
}
}
pub const NAMES: [&str; 2] = ["erga", "erg"];
pub struct Linked {
pub path: std::path::PathBuf,
pub on_path: bool,
}
fn path_dirs() -> Vec<std::path::PathBuf> {
std::env::var_os("PATH")
.map(|p| std::env::split_paths(&p).collect())
.unwrap_or_default()
}
pub fn place_link(exe: &std::path::Path) -> Option<Linked> {
let home = std::env::var_os("HOME").map(std::path::PathBuf::from);
let on_path = path_dirs();
let is_on_path = |d: &std::path::Path| on_path.iter().any(|p| p == d);
let mut targets = vec![std::path::PathBuf::from("/usr/local/bin")];
let mine = |d: &std::path::PathBuf| home.as_ref().is_some_and(|h| d.starts_with(h));
targets.extend(on_path.iter().filter(|d| mine(d)).cloned());
targets.extend(on_path.iter().filter(|d| !mine(d) && d.starts_with("/opt")).cloned());
if let Some(h) = &home {
targets.push(h.join(".local/bin"));
}
for dir in targets {
if !dir.exists() && std::fs::create_dir_all(&dir).is_err() {
continue;
}
let Some(path) = link_as(exe, &dir, NAMES[0]) else { continue };
for alias in &NAMES[1..] {
link_as(exe, &dir, alias);
}
let on_path = is_on_path(&dir);
return Some(Linked { path, on_path });
}
None
}
fn link_as(exe: &std::path::Path, dir: &std::path::Path, name: &str) -> Option<std::path::PathBuf> {
let link = dir.join(name);
if std::fs::read_link(&link).is_ok_and(|t| t == exe) {
return Some(link); }
let _ = std::fs::remove_file(&link);
std::os::unix::fs::symlink(exe, &link).ok().map(|()| link)
}
pub fn link_quietly() {
if let Ok(exe) = std::env::current_exe() {
if exe.to_string_lossy().contains(".app/Contents/MacOS/") {
if let Some(l) = place_link(&exe) {
store::log(&format!(
"cli linked at {} (on PATH: {})",
l.path.display(),
l.on_path
));
}
}
}
}