use cyb_core::{Cell, MoneyEvent, MoneyWallet, Signal, money_to_sense};
use inf_value::Value;
use std::io::{self, BufRead, IsTerminal, Read, Write};
use std::path::Path;
fn tty() -> bool {
io::stdout().is_terminal()
}
fn paint(code: &str, s: &str) -> String {
if tty() {
format!("\x1b[{code}m{s}\x1b[0m")
} else {
s.to_string()
}
}
fn dim(s: &str) -> String {
paint("90", s)
}
fn cyan(s: &str) -> String {
paint("36", s)
}
fn green(s: &str) -> String {
paint("32", s)
}
fn yellow(s: &str) -> String {
paint("33", s)
}
fn bold(s: &str) -> String {
paint("1", s)
}
fn red(s: &str) -> String {
paint("31", s)
}
const LOGO: &str = "\
\x1b[31m โโโโโโโโโโ โโโโโโโโโโ \x1b[0m
\x1b[33mโโโโโโโโโโโโ โโโโโโโโโโโโ\x1b[0m
\x1b[32mโโโ โโโโโโโ โโโโโโโโ\x1b[0m
\x1b[36mโโโ โโโโโ โโโโโโโโ\x1b[0m
\x1b[34mโโโโโโโโ โโโ โโโโโโโโ\x1b[0m
\x1b[35m โโโโโโโ โโโ โโโโโโโ \x1b[0m";
fn banner() -> String {
if !tty() {
return String::new();
}
format!(
"{LOGO}\n{tag}\n{params}\n",
tag = paint("37", " an immortal robot"),
params = dim("\n Goldilocks field ยท p = 2^64 - 2^32 + 1\n \
cyberlink graph ยท per-neuron signal chains\n \
event-sourced ยท never forgets ยท converges to ฯ*\n"),
)
}
fn pid(label: &str) -> [u8; 32] {
let mut p = [0u8; 32];
let b = label.as_bytes();
let n = b.len().min(32);
p[..n].copy_from_slice(&b[..n]);
p
}
fn particle(h: &[u8; 32]) -> String {
let end = h.iter().position(|&b| b == 0).unwrap_or(32);
if end > 0 && h[..end].iter().all(u8::is_ascii_graphic) && h[end..].iter().all(|&b| b == 0) {
String::from_utf8_lossy(&h[..end]).into_owned()
} else {
let hex: String = h[..3].iter().map(|b| format!("{b:02x}")).collect();
format!("{hex}โฆ")
}
}
fn cell_str(v: &Value) -> String {
match v {
Value::Hash(h) => particle(h),
Value::Int(n) => n.to_string(),
Value::Bool(b) => b.to_string(),
Value::Word(w) => w.to_string(),
Value::Bytes(b) => String::from_utf8_lossy(b).into_owned(),
Value::Null => "ยท".into(),
other => format!("{other:?}"),
}
}
fn print_table(cols: &[String], rows: &[Vec<Value>]) {
let ncol = cols.len();
let mut width: Vec<usize> = cols.iter().map(|c| c.chars().count()).collect();
let text: Vec<Vec<String>> = rows
.iter()
.map(|r| r.iter().map(cell_str).collect::<Vec<_>>())
.collect();
for row in &text {
for (i, cell) in row.iter().enumerate().take(ncol) {
width[i] = width[i].max(cell.chars().count());
}
}
let header: String = cols
.iter()
.enumerate()
.map(|(i, c)| format!("{:<w$}", c, w = width[i]))
.collect::<Vec<_>>()
.join(" ");
println!(" {}", dim(&header));
for (row, vals) in text.iter().zip(rows) {
let cells: Vec<String> = row
.iter()
.enumerate()
.take(ncol)
.map(|(i, cell)| {
let padded = format!("{:<w$}", cell, w = width[i]);
match (i, vals.get(i)) {
(0, _) => cyan(&padded),
(_, Some(Value::Int(_))) => yellow(&padded),
_ => padded,
}
})
.collect();
println!(" {}", cells.join(" "));
}
if rows.is_empty() {
println!(" {}", dim("(empty)"));
}
}
fn help() {
let rows = [
("id", "this cyb's neuron + pussy address"),
("link <a> <b>", "assert a cyberlink a โ b"),
("bind <claim>", "record a verified mudra migration claim"),
("fund <token> <amt>", "seed balance for tests (genesis)"),
("balance [token]", "show balance (default token CYB)"),
("send <to> <token> <amt>", "pay coins (grade-2 finality)"),
(
"earn <a> <b> [amt]",
"link aโb + settle Shapley reward (clock B)",
),
("events", "drain money events (sigma feed)"),
("sense", "money events as sense NOTIFY"),
("finalize", "advance block height (mature settle rewards)"),
("query [inf]", "the graph's nodes (or run a raw inf script)"),
("axons", "the graph's edges: from โ to, with weight"),
(
"log",
"the signal log โ the event history state derives from",
),
(
"pull <path>",
"absorb a peer's signal log (file anti-entropy)",
),
("state", "how many nodes, axons, and signals the cell holds"),
(
"tools",
"the cyber toolset (hemera, nox, โฆ) โ run any by name",
),
(
"install <tโฆ>",
"build tools from the registry onto PATH (--all)",
),
("help ยท quit", "this ยท leave"),
];
let w = rows.iter().map(|(c, _)| c.len()).max().unwrap_or(0);
println!("{}", dim("commands"));
for (cmd, desc) in rows {
println!(" {} {}", bold(&format!("{cmd:<w$}")), dim(desc));
}
let sep = dim(" ยท ");
println!(
"\n {}\n {}",
dim("one-shot"),
[
green("cy link cat dog"),
green("cy bind <claim>"),
green("cy axons"),
green("cy log")
]
.join(&sep),
);
}
fn show_log(cell: &Cell) {
let sigs = cell.signals();
if sigs.is_empty() {
println!(" {}", dim("(no signals yet)"));
return;
}
for s in &sigs {
let sh: String = s.hash()[..3].iter().map(|b| format!("{b:02x}")).collect();
print_signal(s, &sh);
}
println!(" {}", dim(&format!("{} signal(s)", sigs.len())));
}
fn print_signal(s: &Signal, short_hash: &str) {
println!(
" {} {} {} {} {} {}",
dim("signal"),
yellow(&format!("{short_hash}โฆ")),
dim("neuron"),
cyan(&particle(&s.neuron)),
dim("step"),
yellow(&s.step.to_string()),
);
for l in &s.links {
println!(
" {} {} {}",
cyan(&particle(&l.from)),
dim("โ"),
cyan(&particle(&l.to))
);
}
}
fn show_nodes(cell: &Cell) {
let nodes = cell.nodes();
if nodes.is_empty() {
println!(" {}", dim("(no nodes yet)"));
return;
}
let labels: Vec<String> = nodes.iter().map(|(p, _)| particle(p)).collect();
let w = labels
.iter()
.map(|l| l.chars().count())
.max()
.unwrap_or(0)
.max(8);
println!(
" {} {}",
dim(&format!("{:<w$}", "particle")),
dim("energy")
);
for ((_, e), label) in nodes.iter().zip(&labels) {
println!(
" {} {}",
cyan(&format!("{label:<w$}")),
yellow(&e.to_string())
);
}
}
fn show_axons(cell: &Cell) {
let axons = cell.axons();
if axons.is_empty() {
println!(" {}", dim("(no axons yet)"));
return;
}
for (from, to, weight) in &axons {
println!(
" {} {} {} {}",
cyan(&particle(from)),
dim("โ"),
cyan(&particle(to)),
dim(&format!("weight {weight}")),
);
}
println!(" {}", dim(&format!("{} axon(s)", axons.len())));
}
#[derive(serde::Deserialize)]
struct Registry {
tool: Vec<Tool>,
}
#[derive(serde::Deserialize)]
struct Tool {
name: String,
dir: String,
pkg: Option<String>,
bin: Option<String>,
alias: Option<String>,
desc: String,
}
impl Tool {
fn bin(&self) -> &str {
self.bin.as_deref().unwrap_or(&self.name)
}
}
fn registry() -> &'static [Tool] {
static REG: std::sync::OnceLock<Vec<Tool>> = std::sync::OnceLock::new();
REG.get_or_init(|| {
toml::from_str::<Registry>(include_str!("../tools.toml"))
.expect("tools.toml is malformed")
.tool
})
}
fn tool(name: &str) -> Option<&'static Tool> {
registry()
.iter()
.find(|t| t.name == name || t.alias.as_deref() == Some(name))
}
fn cyber_root() -> std::path::PathBuf {
if let Some(r) = std::env::var_os("CYBER_ROOT") {
return std::path::PathBuf::from(r);
}
let home = std::env::var("HOME").unwrap_or_else(|_| ".".into());
std::path::Path::new(&home).join("cyber")
}
fn bin_dir() -> std::path::PathBuf {
let home = std::env::var("HOME").unwrap_or_else(|_| ".".into());
std::path::Path::new(&home).join(".cargo").join("bin")
}
fn split_args(s: &str) -> Vec<String> {
let mut args = Vec::new();
let mut cur = String::new();
let mut open = false; let (mut in_single, mut in_double) = (false, false);
for c in s.chars() {
match c {
'\'' if !in_double => in_single = !in_single,
'"' if !in_single => in_double = !in_double,
c if c.is_whitespace() && !in_single && !in_double => {
if open {
args.push(std::mem::take(&mut cur));
open = false;
}
continue;
}
c => cur.push(c),
}
open = true;
}
if open {
args.push(cur);
}
args
}
const BUILTINS: &[&str] = &[
"id", "whoami", "link", "query", "axons", "edges", "pull", "bind", "log", "state", "tools",
"deps", "install", "fund", "balance", "send", "earn", "events", "sense", "finalize", "help",
"?", "quit", "exit", "q", "",
];
fn is_builtin(cmd: &str) -> bool {
BUILTINS.contains(&cmd)
}
fn dispatch_argv(name: &str, args: &[String]) {
match std::process::Command::new(name).args(args).status() {
Ok(_) => {}
Err(e) if e.kind() == std::io::ErrorKind::NotFound => {
println!(
" {} not installed โ {}",
bold(name),
green(&format!("cy install {name}"))
);
}
Err(e) => println!(" {}: {name}: {e}", paint("31", "error")),
}
}
fn dispatch(name: &str, rest: &str) {
dispatch_argv(name, &split_args(rest));
}
fn show_tools() {
println!(
"{}",
dim("the cyber toolset โ `cy <name> โฆ` runs one ยท `cy install --all` builds them")
);
for t in registry() {
let link = bin_dir().join(&t.name);
let (mark, note) = if std::fs::metadata(&link).is_ok() {
(green("โ"), "") } else if std::fs::symlink_metadata(&link).is_ok() {
(yellow("โ "), " โ stale, run `cy install`") } else {
(dim("โ"), "") };
let alias = t
.alias
.as_deref()
.map(|a| format!(" {}", dim(&format!("({a})"))))
.unwrap_or_default();
println!(
" {} {} {}{}{}",
mark,
bold(&format!("{:<10}", t.name)),
dim(&t.desc),
alias,
dim(note)
);
}
}
fn cmd_install(rest: &str) {
let names = split_args(rest);
let all = names.is_empty() || names.iter().any(|n| n == "--all" || n == "all");
let targets: Vec<&Tool> = if all {
registry().iter().collect()
} else {
names
.iter()
.filter_map(|n| {
tool(n).or_else(|| {
println!(" {}: not a registered tool (see `cy tools`)", red(n));
None
})
})
.collect()
};
if targets.is_empty() {
return;
}
let root = cyber_root();
let _ = std::fs::create_dir_all(bin_dir());
let (mut ok, mut fail) = (0u32, 0u32);
for t in targets {
if install_one(t, &root) {
ok += 1;
} else {
fail += 1;
}
}
let tail = if fail > 0 {
red(&format!(", {fail} failed"))
} else {
String::new()
};
println!(" {} {ok} installed{tail}", green("โ"));
}
fn install_one(t: &Tool, root: &Path) -> bool {
println!(" {} {}", dim("building"), bold(&t.name));
let mut cargo = std::process::Command::new("cargo");
cargo
.arg("build")
.arg("--release")
.current_dir(root.join(&t.dir))
.env("RUSTC_BOOTSTRAP", "1"); if let Some(pkg) = &t.pkg {
for p in pkg.split_whitespace() {
cargo.arg("-p").arg(p);
}
}
if !cargo.status().map(|s| s.success()).unwrap_or(false) {
println!(" {} {} โ build failed", red("โ"), bold(&t.name));
return false;
}
let target = root.join(&t.dir).join("target/release").join(t.bin());
if !link_bin(&target, &t.name) {
return false;
}
if let Some(alias) = &t.alias {
if !link_bin(&target, alias) {
return false;
}
}
true
}
fn link_bin(target: &Path, name: &str) -> bool {
let link = bin_dir().join(name);
let _ = std::fs::remove_file(&link);
match std::os::unix::fs::symlink(target, &link) {
Ok(()) => {
println!(
" {} {} {} {}",
green("โ"),
bold(name),
dim("โ"),
dim(&link.display().to_string())
);
true
}
Err(e) => {
println!(" {}: link {}: {e}", red("error"), name);
false
}
}
}
fn pull(cell: &mut Cell, peer: &Path) {
match std::fs::File::open(peer) {
Ok(mut f) => {
let mut bytes = Vec::new();
if f.read_to_end(&mut bytes).is_ok() {
let n = cell.absorb(&bytes);
let tag = if n == 0 {
dim("already in sync")
} else {
green(&format!("+{n} new"))
};
println!(" {} {} {}", dim("pull"), peer.display(), tag);
} else {
println!(
" {}: could not read {}",
paint("31", "error"),
peer.display()
);
}
}
Err(e) => println!(" {}: {} โ {e}", paint("31", "error"), peer.display()),
}
}
fn bind(cell: &mut Cell, claim_str: &str) {
let Some(c) = mudra::Claim::decode(claim_str) else {
println!(
" {}: malformed claim (expected: address pubkey neuron signature)",
paint("31", "error")
);
return;
};
if !mudra::claim::verify(&c, mudra::cosmos::PUSSY) {
println!(" {} {}", paint("31", "โ"), bold("claim does not verify"));
return;
}
let mut legacy = [0u8; 32];
legacy[..20].copy_from_slice(&mudra::cosmos::account_id(&c.pubkey));
match cell.link(c.neuron, legacy, c.neuron) {
Ok(sig) => {
let s: String = sig[..3].iter().map(|b| format!("{b:02x}")).collect();
println!(
" {} {} {} {} {}",
green("โ"),
cyan(&c.address),
dim("โ neuron"),
cyan(&particle(&c.neuron)),
dim(&format!("signal {s}โฆ")),
);
println!(
" {}",
dim("migration recorded โ the neuron now owns its legacy address")
);
}
Err(e) => println!(" {}: {e:?}", paint("31", "error")),
}
}
fn print_money_event(e: &MoneyEvent) {
match e {
MoneyEvent::TransferOut {
to,
amount,
token,
signal,
..
} => println!(
" {} {} {} {} {}",
yellow("out"),
cyan(&particle(to)),
yellow(&amount.to_string()),
dim(&particle(token)),
dim(&format!("sig {}", &hex3(signal))),
),
MoneyEvent::TransferIn {
from,
amount,
token,
signal,
..
} => println!(
" {} {} {} {} {}",
green("in"),
cyan(&particle(from)),
yellow(&amount.to_string()),
dim(&particle(token)),
dim(&format!("sig {}", &hex3(signal))),
),
MoneyEvent::RewardCredited {
amount,
token,
reason,
clock,
..
} => println!(
" {} {:?} {} {} {}",
green("reward"),
clock,
yellow(&amount.to_string()),
dim(&particle(token)),
dim(&format!("reason {}", &hex3(reason))),
),
MoneyEvent::Finalized { signal, .. } => {
println!(
" {} {}",
green("final"),
dim(&format!("sig {}", &hex3(signal)))
);
}
MoneyEvent::TipAdvanced { height, grade4, .. } => println!(
" {} h={} grade4={}",
dim("tip"),
yellow(&height.to_string()),
if *grade4 { green("yes") } else { red("no") }
),
MoneyEvent::BalanceUpdated {
amount,
token,
tip_height,
..
} => println!(
" {} {} {} @h{}",
dim("bal"),
yellow(&amount.to_string()),
dim(&particle(token)),
tip_height
),
MoneyEvent::FinalityFailed { reason, .. } => {
println!(" {} {}", red("fail"), dim(reason));
}
}
}
fn exec(cell: &mut Cell, wallet: &mut MoneyWallet, id: &Id, line: &str) -> bool {
let mut it = line.trim().splitn(2, ' ');
match it.next().unwrap_or("") {
"id" | "whoami" => show_id(id),
"fund" => {
let mut ab = it.next().unwrap_or("").split_whitespace();
match (ab.next(), ab.next()) {
(Some(tok), Some(amt)) => {
let amount: u64 = amt.parse().unwrap_or(0);
wallet.fund_for_test(cell, pid(tok), amount);
println!(
" {} funded {} {} tip grade4={}",
green("โ"),
yellow(&amount.to_string()),
cyan(tok),
if wallet.grade4() {
green("yes")
} else {
red("no")
}
);
}
_ => println!(" {}: fund <token> <amount>", dim("usage")),
}
}
"balance" => {
let tok = it
.next()
.map(str::trim)
.filter(|s| !s.is_empty())
.unwrap_or("CYB");
let token = pid(tok);
if !wallet.grade4() {
wallet.sync_tip_local(cell);
}
match wallet.open_balance(cell, &id.neuron, &token) {
Ok((amt, _)) => println!(
" {} {} {} {}",
dim("balance"),
yellow(&amt.to_string()),
cyan(tok),
dim(&format!("(tip h={})", wallet.tip().height)),
),
Err(e) => println!(" {}: {e:?}", paint("31", "error")),
}
}
"send" => {
let mut ab = it.next().unwrap_or("").split_whitespace();
match (ab.next(), ab.next(), ab.next()) {
(Some(to), Some(tok), Some(amt)) => {
let amount: u64 = amt.parse().unwrap_or(0);
if !wallet.grade4() {
wallet.sync_tip_local(cell);
}
match wallet.send(cell, pid(to), pid(tok), amount) {
Ok((sig, ev)) => {
println!(
" {} {} {} {} โ {} {}",
green("โ"),
yellow(&amount.to_string()),
cyan(tok),
dim("to"),
cyan(to),
dim(&format!(
"sig {}โฆ final={}",
&hex3(&sig),
ev.verify(wallet.tip())
)),
);
}
Err(e) => println!(" {}: {e:?}", paint("31", "error")),
}
}
_ => println!(" {}: send <to> <token> <amount>", dim("usage")),
}
}
"earn" => {
let mut ab = it.next().unwrap_or("").split_whitespace();
match (ab.next(), ab.next()) {
(Some(a), Some(b)) => {
let stake: u64 = ab.next().and_then(|s| s.parse().ok()).unwrap_or(8000);
if !wallet.grade4() {
wallet.sync_tip_local(cell);
}
if wallet.pending_claims().is_empty() {
use tru::Link;
let base = vec![
Link::stake(pid("x"), pid("y"), 100),
Link::stake(pid("y"), pid("z"), 100),
Link::stake(pid("z"), pid("x"), 100),
];
wallet.set_reward_base(base);
}
wallet.reward_token = pid("CYB");
wallet.reward_budget = 1000;
wallet.use_live_epoch = true;
wallet.settle_depth = 1;
match wallet.link_and_settle(cell, pid(a), pid(b), stake, 1) {
Ok((sid, rec, minted)) => {
println!(
" {} {} {} {} stake={} {}",
green("โ"),
cyan(a),
dim("โ"),
cyan(b),
yellow(&stake.to_string()),
dim(&format!("sig {}โฆ", &hex3(&sid))),
);
println!(
" {} minted {} CYB epoch={} receipt {}โฆ",
green("reward"),
yellow(&minted.to_string()),
yellow(&rec.epoch.to_string()),
dim(&hex3(&rec.receipt_hash)),
);
println!(
" {} run `finalize` once to mature clock-B (depth={})",
dim("tip"),
wallet.settle_depth
);
}
Err(e) => println!(" {}: {e:?}", paint("31", "error")),
}
}
_ => println!(" {}: earn <from> <to> [stake]", dim("usage")),
}
}
"events" => {
let ev = wallet.drain_events();
if ev.is_empty() {
println!(" {}", dim("(no money events)"));
} else {
for e in &ev {
print_money_event(e);
}
}
}
"sense" => {
let ev = wallet.drain_events();
let notes = money_to_sense(id.neuron, &ev);
if notes.is_empty() {
println!(" {}", dim("(no sense notifications)"));
} else {
for n in notes {
println!(
" {} {} {} {} {}",
green("notify"),
cyan(n.kind),
yellow(&n.amount.to_string()),
dim(&particle(&n.token)),
dim(&format!("reason {}", &hex3(&n.reason))),
);
}
}
}
"finalize" => {
wallet.finalize_block(cell);
let ready = wallet.mature_settles();
println!(
" {} block tip h={} grade4={}",
green("โ"),
yellow(&wallet.tip().height.to_string()),
if wallet.grade4() {
green("yes")
} else {
red("no")
}
);
for (tok, amt, reason) in ready {
println!(
" {} settle mature {} {} {}",
green("reward"),
yellow(&amt.to_string()),
dim(&particle(&tok)),
dim(&format!("reason {}", &hex3(&reason))),
);
}
}
"link" => {
let mut ab = it.next().unwrap_or("").split_whitespace();
match (ab.next(), ab.next()) {
(Some(a), Some(b)) => match cell.link(id.neuron, pid(a), pid(b)) {
Ok(sig) => {
let s: String = sig[..3].iter().map(|b| format!("{b:02x}")).collect();
println!(
" {} {} {} {} {}",
green("โ"),
cyan(a),
dim("โ"),
cyan(b),
dim(&format!("signal {s}โฆ")),
);
}
Err(e) => println!(" {}: {e:?}", paint("31", "error")),
},
_ => println!(" {}: link <a> <b>", dim("usage")),
}
}
"query" => match it.next() {
None => show_nodes(cell),
Some(inf) => match cell.query(inf) {
Ok(out) => print_table(&out.columns, &out.rows),
Err(e) => println!(" {}: {e:?}", paint("31", "error")),
},
},
"axons" | "edges" => show_axons(cell),
"pull" => match it.next() {
Some(path) => pull(cell, Path::new(path.trim())),
None => println!(" {}: pull <peer-log-path>", dim("usage")),
},
"bind" => match it.next() {
Some(claim) => bind(cell, claim.trim()),
None => println!(" {}: bind <claim> (from `mudra claim โฆ`)", dim("usage")),
},
"log" => show_log(cell),
"state" => println!(
" {} {} {} {} {} {} {} {}",
yellow(&cell.nodes().len().to_string()),
dim("nodes"),
dim("ยท"),
yellow(&cell.axons().len().to_string()),
dim("axons"),
dim("ยท"),
yellow(&cell.len().to_string()),
dim("signals"),
),
"tools" | "deps" => show_tools(),
"install" => cmd_install(it.next().unwrap_or("")),
"help" | "?" => help(),
"quit" | "exit" | "q" => return false,
"" => {}
other => {
if tool(other).is_some() {
dispatch(other, it.next().unwrap_or(""));
} else {
println!(
" {}: {other} {}",
dim("unknown"),
dim("(try: help ยท tools)")
);
}
}
}
true
}
fn default_path() -> std::path::PathBuf {
cyb_dir().join("graph.log")
}
fn cyb_dir() -> std::path::PathBuf {
let home = std::env::var("HOME").unwrap_or_else(|_| ".".into());
std::path::Path::new(&home).join("cyb")
}
struct Id {
neuron: [u8; 32],
pubkey: [u8; 33],
address: String,
}
fn identity() -> Id {
let dir = cyb_dir();
let _ = std::fs::create_dir_all(&dir);
let file = dir.join("mnemonic");
let mnemonic = match std::fs::read_to_string(&file) {
Ok(m) if !m.trim().is_empty() => m.trim().to_string(),
_ => {
let m = mudra::seed::generate_mnemonic().expect("generate mnemonic");
let _ = std::fs::write(&file, format!("{m}\n"));
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
let _ = std::fs::set_permissions(&file, std::fs::Permissions::from_mode(0o600));
}
eprintln!(
" {} new neuron minted ยท {}",
green("โฆ"),
dim(&file.display().to_string())
);
eprintln!(
" {}",
dim("back up this mnemonic โ it is the only key to this neuron")
);
m
}
};
let key = mudra::seed::cosmos_key(&mnemonic, "").expect("derive key from mnemonic");
let pubkey = mudra::cosmos::compressed(key.verifying_key());
let neuron = mudra::claim::neuron_of(&pubkey);
let address = mudra::cosmos::address(&pubkey, mudra::cosmos::PUSSY).unwrap_or_default();
Id {
neuron,
pubkey,
address,
}
}
fn show_id(id: &Id) {
let hx = |b: &[u8]| -> String { b.iter().map(|x| format!("{x:02x}")).collect() };
println!(" {} {}", dim("neuron "), green(&hx(&id.neuron)));
println!(" {} {}", dim("pubkey "), dim(&hx(&id.pubkey)));
println!(" {} {}", dim("pussy "), cyan(&id.address));
}
fn main() {
let id = identity();
let path = default_path();
let mut cell = Cell::open(&path).unwrap_or_else(|_| Cell::ephemeral());
let mut wallet = MoneyWallet::new(id.neuron).with_tip_prover();
wallet.sync_tip_local(&cell);
let args: Vec<String> = std::env::args().skip(1).collect();
if matches!(
args.first().map(String::as_str),
Some("help" | "--help" | "-h")
) {
print!("{}", banner());
help();
return;
}
if !args.is_empty() {
let first = args[0].as_str();
if is_builtin(first) {
exec(&mut cell, &mut wallet, &id, &args.join(" "));
} else if tool(first).is_some() {
dispatch_argv(first, &args[1..]);
} else {
println!(
" {}: {first} {}",
dim("unknown"),
dim("(try: help ยท tools)")
);
}
return;
}
print!("{}", banner());
println!(
" {} {} {}",
dim(&path.display().to_string()),
dim("ยท"),
dim(&format!(
"neuron {}โฆ ยท {} nodes ยท type `help`",
&hex3(&id.neuron),
cell.nodes().len()
)),
);
let prompt = format!("{}{} ", cyan("cyb"), dim("โบ"));
let stdin = io::stdin();
loop {
print!("{prompt}");
io::stdout().flush().ok();
let mut line = String::new();
if stdin.lock().read_line(&mut line).unwrap_or(0) == 0 {
println!();
break;
}
if !exec(&mut cell, &mut wallet, &id, &line) {
break;
}
}
}
fn hex3(b: &[u8]) -> String {
b[..3].iter().map(|x| format!("{x:02x}")).collect()
}