//! `hivectl forge create-user ` — provision a Forgejo account via the //! daemon (which owns the forge admin token + account-token persistence); //! hivectl just resolves the password client-side and relays the request. use std::io::{self, Write}; use std::path::Path; use anyhow::Result; use hive_host_sock::{HostRequest, ReconcileDirection}; use crate::cli::ReconcileFrom; use crate::util::{daemon_request, resolve_password}; pub(crate) async fn forge_create_user( socket: &Path, name: &str, password: Option<&str>, password_stdin: bool, ) -> Result<()> { // Resolve the password client-side (inline flag or stdin read); the // daemon never touches this process's stdin. The is-present check, the // agent-vs-operator branch, and token persistence now live in the // daemon handler. let password = resolve_password(password, password_stdin)?; daemon_request( socket, hive_host_sock::HostRequest::ForgeCreateUser { name: crate::util::parse_ident(name)?, password, }, "forge", ) .await } /// `hivectl forge reconcile-config [--from ] [--verbose]`. /// Always shows the divergence first (daemon computes it read-only), then /// applies the chosen direction — from `--from` or an interactive prompt. pub(crate) async fn forge_reconcile_config( socket: &Path, agent: &str, from: Option, verbose: bool, ) -> Result<()> { daemon_request( socket, HostRequest::ReconcileConfigStatus { agent: crate::util::parse_ident(agent)?, verbose, }, "forge", ) .await?; let direction = match from { Some(f) => Some(f.into()), None => prompt_direction()?, }; let Some(direction) = direction else { println!("forge: aborted — no changes made"); return Ok(()); }; daemon_request( socket, HostRequest::ReconcileConfigApply { agent: crate::util::parse_ident(agent)?, direction, }, "forge", ) .await } /// Prompt the operator for the reconcile direction after showing the diff. /// Returns `None` to abort (empty / `q` / unrecognized input). fn prompt_direction() -> Result> { print!("reconcile from [forge/local] (or q to abort): "); io::stdout().flush()?; let mut line = String::new(); io::stdin().read_line(&mut line)?; match line.trim().to_ascii_lowercase().as_str() { "forge" | "f" => Ok(Some(ReconcileDirection::Forge)), "local" | "l" => Ok(Some(ReconcileDirection::Local)), "" | "q" | "quit" => Ok(None), other => { println!("forge: unrecognized direction {other:?} — aborting"); Ok(None) } } }