refactor(#2352): route hivectl forge create-user through a daemon wire command

This commit is contained in:
damocles 2026-07-14 21:51:15 +02:00 committed by mara
commit a3c916813b
3 changed files with 88 additions and 30 deletions

View file

@ -187,6 +187,9 @@ async fn dispatch(req: &HostRequest, coord: Arc<Coordinator>) -> 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;
@ -346,6 +349,46 @@ 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<HostResponse> {
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<HostResponse> {
require_matrix_present().await?;
let register_token =