⎇ 53dbdf0f1deea10d6fc567ea2b43e1e75d6d60cd
crates/modules/semantic_dht/minga-cli/src/main.rs
History
Who touched what
raw
chore: monorepo inicial con arje + minga + yahweh absorbidos
1//! Binario `minga`: argument parsing y formateo de salida.
2
3use std::path::PathBuf;
4use std::process::ExitCode;
5
6use clap::{Parser, Subcommand};
7use minga_cli::{
8 cmd_ingest, cmd_init, cmd_listen, cmd_status, cmd_sync, cmd_watch, CliError,
9};
10
11#[derive(Parser)]
12#[command(
13 name = "minga",
14 version,
15 about = "Minga: VCS semántico P2P. Versiona AST, no líneas."
16)]
17struct Cli {
18 /// Ruta del repositorio Minga. Por defecto: `.minga` en el cwd.
19 #[arg(short, long, default_value = ".minga", global = true)]
20 repo: PathBuf,
21
22 #[command(subcommand)]
23 command: Command,
24}
25
26#[derive(Subcommand)]
27enum Command {
28 /// Inicializa un nuevo repo: genera keypair Ed25519, lo cifra con
29 /// passphrase, y crea el almacén persistente vacío.
30 Init,
31
32 /// Muestra DID, tamaño del MST, nodos almacenados y atestaciones.
33 Status,
34
35 /// Parsea un archivo Rust, lo añade al MST y firma una atestación
36 /// de autoría con la identidad del repo.
37 Ingest {
38 /// Ruta del archivo .rs a ingerir.
39 file: PathBuf,
40 },
41
42 /// Escucha conexiones de peers en una multiaddr libp2p y acepta
43 /// sincronizaciones entrantes hasta Ctrl+C.
44 Listen {
45 /// Multiaddr libp2p, ej. `/ip4/0.0.0.0/tcp/4001`.
46 addr: String,
47 },
48
49 /// Sincroniza una vez con un peer remoto (multiaddr con `/p2p/<id>`).
50 Sync {
51 /// Multiaddr completo, ej. `/ip4/1.2.3.4/tcp/4001/p2p/12D3KooW...`.
52 peer: String,
53 },
54
55 /// Vigila un directorio y re-ingiere automáticamente cualquier
56 /// archivo `.rs` que se cree o modifique. Minga como VCS de fondo:
57 /// el usuario escribe en su editor y el código queda versionado.
58 Watch {
59 /// Directorio a vigilar.
60 dir: PathBuf,
61 },
62}
63
64fn main() -> ExitCode {
65 match run() {
66 Ok(()) => ExitCode::SUCCESS,
67 Err(e) => {
68 eprintln!("error: {}", e);
69 ExitCode::FAILURE
70 }
71 }
72}
73
74fn run() -> Result<(), CliError> {
75 let cli = Cli::parse();
76
77 match cli.command {
78 Command::Init => {
79 let pass = prompt_passphrase_with_confirm()?;
80 let did = cmd_init(&cli.repo, &pass)?;
81 println!("Repo inicializado en {}", cli.repo.display());
82 println!("DID: {}", did);
83 }
84 Command::Status => {
85 let pass = prompt_passphrase()?;
86 let s = cmd_status(&cli.repo, &pass)?;
87 println!("DID: {}", s.did);
88 println!("MST: {} claves", s.mst_len);
89 println!("Nodos almacenados: {}", s.nodes_len);
90 println!("Atestaciones: {}", s.attestations_len);
91 }
92 Command::Ingest { file } => {
93 let pass = prompt_passphrase()?;
94 let r = cmd_ingest(&cli.repo, &pass, &file)?;
95 println!("Ingerido: {}", file.display());
96 println!("Hash: {}", r.hash);
97 println!("Firmado por: {}", r.did);
98 }
99 Command::Listen { addr } => {
100 let pass = prompt_passphrase()?;
101 let rt = tokio::runtime::Runtime::new()
102 .map_err(|e| CliError::Io(std::io::Error::new(std::io::ErrorKind::Other, e)))?;
103 rt.block_on(cmd_listen(&cli.repo, &pass, &addr))?;
104 }
105 Command::Sync { peer } => {
106 let pass = prompt_passphrase()?;
107 let rt = tokio::runtime::Runtime::new()
108 .map_err(|e| CliError::Io(std::io::Error::new(std::io::ErrorKind::Other, e)))?;
109 rt.block_on(cmd_sync(&cli.repo, &pass, &peer))?;
110 println!("Sync completo.");
111 }
112 Command::Watch { dir } => {
113 let pass = prompt_passphrase()?;
114 println!("Vigilando {}. Ctrl+C para parar.", dir.display());
115 let rt = tokio::runtime::Runtime::new()
116 .map_err(|e| CliError::Io(std::io::Error::new(std::io::ErrorKind::Other, e)))?;
117 rt.block_on(cmd_watch(&cli.repo, &pass, &dir))?;
118 }
119 }
120 Ok(())
121}
122
123fn prompt_passphrase() -> Result<String, CliError> {
124 let pass = rpassword::prompt_password("Passphrase: ")
125 .map_err(CliError::Io)?;
126 Ok(pass)
127}
128
129fn prompt_passphrase_with_confirm() -> Result<String, CliError> {
130 let pass = rpassword::prompt_password("Passphrase nueva: ")
131 .map_err(CliError::Io)?;
132 let conf = rpassword::prompt_password("Confirma: ")
133 .map_err(CliError::Io)?;
134 if pass != conf {
135 return Err(CliError::Io(std::io::Error::new(
136 std::io::ErrorKind::InvalidInput,
137 "passphrases no coinciden",
138 )));
139 }
140 Ok(pass)
141}