diff --git a/hive-c0re/src/bin/hivectl.rs b/hive-c0re/src/bin/hivectl.rs index 8cd2ec63..143e0657 100644 --- a/hive-c0re/src/bin/hivectl.rs +++ b/hive-c0re/src/bin/hivectl.rs @@ -688,7 +688,7 @@ async fn main() -> Result<()> { name, password, password_stdin, - } => forge_create_user(&socket, &name, password.as_deref(), password_stdin).await, + } => forge_create_user(&name, password.as_deref(), password_stdin).await, }, Cmd::Matrix { cmd } => run_matrix_cmd(&socket, cmd).await, Cmd::Github { cmd } => match cmd { @@ -1258,42 +1258,38 @@ async fn github_set_token(agent: &str, token: Option, token_stdin: bool) Ok(()) } -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)?; - forge_request( - socket, - hive_host_sock::HostRequest::ForgeCreateUser { - name: name.to_owned(), - password, - }, - ) - .await -} - -/// Send a forge provisioning request to the daemon and print its result -/// lines. Mirrors [`matrix_request`] — the daemon owns the provisioning -/// logic; hivectl just relays the outcome. -async fn forge_request(socket: &Path, req: hive_host_sock::HostRequest) -> Result<()> { - let resp = hive_c0re::client::request(socket, req) - .await - .with_context(|| format!("connect to daemon socket {}", socket.display()))?; - if !resp.ok { +async fn forge_create_user(name: &str, password: Option<&str>, password_stdin: bool) -> Result<()> { + if !hive_c0re::forge::is_present().await { bail!( - "forge: {}", - resp.error.as_deref().unwrap_or("unknown error") + "hive-forge container not running — wait for hive-c0re to start it before provisioning forge users" ); } - for line in &resp.messages { - println!("{line}"); + let user_password = resolve_password(password, password_stdin)?; + if agent_exists(name)? { + if user_password.is_some() { + bail!( + "forge create-user: --password / --password-stdin is for non-agent (operator) accounts only; '{name}' is an agent which authenticates via API token" + ); + } + hive_c0re::forge::ensure_user_for(name) + .await + .with_context(|| format!("forge create-user {name}"))?; + let path = Coordinator::agent_notes_dir(name).join("forge-token"); + println!("forge: provisioned agent user '{name}'"); + println!("token persisted at: {}", path.display()); + } else { + let token = hive_c0re::forge::provision_user_token(name, user_password.as_deref()) + .await + .with_context(|| format!("forge create-user {name}"))?; + println!("forge: provisioned user '{name}' (not an agent — token not persisted)"); + println!("token: {token}"); + if password.is_some() || password_stdin { + println!("password: set as supplied — use it to log into the forge web UI"); + } else { + println!( + "password: random throwaway (not surfaced — pass --password or --password-stdin to set one you can use)" + ); + } } Ok(()) } diff --git a/hive-c0re/src/server.rs b/hive-c0re/src/server.rs index baafde31..5e88d8a1 100644 --- a/hive-c0re/src/server.rs +++ b/hive-c0re/src/server.rs @@ -74,12 +74,6 @@ async fn handle(stream: UnixStream, coord: Arc) -> Result<()> { } } -#[allow( - clippy::too_many_lines, - reason = "flat one-arm-per-HostRequest-variant router; each arm just \ - delegates to a handler. Splitting the match would scatter the \ - wire-command routing without shrinking it." -)] async fn dispatch(req: &HostRequest, coord: Arc) -> HostResponse { let result: anyhow::Result = async { Ok(match req { @@ -193,9 +187,6 @@ async fn dispatch(req: &HostRequest, coord: Arc) -> HostResponse { HostRequest::MatrixInvite { user, room } => { handle_matrix_invite(user, room.as_deref()).await? } - HostRequest::ForgeCreateUser { name, password } => { - handle_forge_create_user(name, password.as_deref()).await? - } }) } .await; @@ -355,46 +346,6 @@ async fn handle_matrix_create_user(name: &str, password: Option<&str>) -> Result Ok(HostResponse::messages(out)) } -async fn handle_forge_create_user(name: &str, password: Option<&str>) -> Result { - if !crate::forge::is_present().await { - anyhow::bail!( - "hive-forge container not running — wait for hive-c0re to start it before provisioning forge users" - ); - } - let mut out = Vec::new(); - if agent_exists(name)? { - if password.is_some() { - // Agents authenticate by API token, never by password — refuse - // rather than silently dropping a supplied one. - anyhow::bail!( - "forge create-user: a password is for non-agent (operator) accounts only; '{name}' is an agent which authenticates via API token" - ); - } - crate::forge::ensure_user_for(name) - .await - .with_context(|| format!("forge create-user {name}"))?; - let path = Coordinator::agent_notes_dir(name).join("forge-token"); - out.push(format!("forge: provisioned agent user '{name}'")); - out.push(format!("token persisted at: {}", path.display())); - } else { - let token = crate::forge::provision_user_token(name, password) - .await - .with_context(|| format!("forge create-user {name}"))?; - out.push(format!( - "forge: provisioned user '{name}' (not an agent — token not persisted)" - )); - out.push(format!("token: {token}")); - if password.is_some() { - out.push("password: set as supplied — use it to log into the forge web UI".to_owned()); - } else { - out.push( - "password: random throwaway (not surfaced — pass --password or --password-stdin to set one you can use)".to_owned(), - ); - } - } - Ok(HostResponse::messages(out)) -} - async fn handle_matrix_sync_admin() -> Result { require_matrix_present().await?; let register_token = diff --git a/hive-host-sock/src/lib.rs b/hive-host-sock/src/lib.rs index a375505f..b55581fc 100644 --- a/hive-host-sock/src/lib.rs +++ b/hive-host-sock/src/lib.rs @@ -147,17 +147,6 @@ pub enum HostRequest { #[serde(default)] room: Option, }, - /// Create or refresh a forge account + API token for `name`. Daemon-side - /// equivalent of `hivectl forge create-user`: for an existing agent it - /// provisions the account and persists the token to `/forge-token`; - /// for a non-agent (operator/human) it mints a user and returns the token - /// in [`HostResponse::messages`]. `password` is resolved client-side - /// (inline flag or stdin) and only meaningful for non-agent accounts. - ForgeCreateUser { - name: String, - #[serde(default)] - password: Option, - }, } /// Selects which container classes a hive-wide [`HostRequest::Stop`] /