Watch
0
0
Fork
You've already forked hyperhive
0
hyperhive/hivectl/src/main.rs
atlas 58ef299c3c hive-c0re: remove the approvals subsystem
Nothing submits an approval since scheduled prompts left hive-c0re, so the
queue only acted on rows written before that.

- hive-c0re: the approvals store and its migrations, approve/deny/
  resolve actions, the ResolveApproval tail node (and meta_update's
  approval_id), POST /api/approve and /api/deny, the approval views on
  /api/state, the approval_resolved dashboard event, the orphan GC,
  destroy/purge failing pending rows, and the approval rows in
  get_loose_ends. DestroyBookkeeping loses the purge flag, which only
  worded the approval-failure note.
- The CancelLooseEnd request goes: its only hive-c0re arm was the
  approval one. cancel_loose_end handles reminder and todo in the harness.
- Wire types: hive_sh4re::approvals (ReminderStats moves to inbox),
  LooseEnd::Approval, CancelLooseEndKind, HelperEvent::ApprovalResolved,
  ToolGroup::Approvals, HostRequest::{Pending,Approve,Deny} and
  HostResponse.approvals. HOST_SOCK_VERSION goes to 2 for the removed
  field.
- hivectl loses the approvals verbs; docs/tools/hivectl-cli.md is
  regenerated.
- The system prompt drops approval_resolved.
- The approvals table stays in broker.sqlite, unread, the same way the
  operator_questions table was left when its store was removed.
- An `approvals` entry still in tool-groups.json is skipped with a warning
  by ToolGroup::parse_list, like any unknown group.
- docs/agent-lifecycle/approvals.md is renamed to config-changes.md so the
  code's doc pointers resolve; its content follows in the docs commit.
2026-10-04 23:55:32 +02:00

125 lines
4.8 KiB
Rust

//! `hivectl` — operator-facing host CLI for hyperhive.
//!
//! A thin client for the `hive-c0re` daemon: it speaks the host admin
//! socket protocol (`hive-host-sock`) and does NOT link the daemon crate.
//! Container lifecycle (`agent <name> <spawn|kill|rebuild|restart|choom|…>`,
//! `list-agents`, `stop` / `start`) and provisioning (`forge` / `matrix` /
//! `gateway`) all forward to the daemon, which owns the broker,
//! the credentials, and the provisioning logic — a running daemon is
//! required for those. A couple of verbs work off local host state
//! directly instead (`wg` / `peer-config` read the mesh key + TLS CA), so
//! they don't need the socket. `agent <name> choom` execs into a container
//! rather than asking the daemon to do anything, but still uses the socket
//! for its "is this an agent?" pre-flight — that answer lives in a
//! directory only the daemon's user can read.
//!
//! One module per subcommand family (see the `mod` list below); `main` is
//! just the clap parse + the top-level dispatch match.
use anyhow::Result;
use clap::Parser;
mod cli;
/// The host admin socket client (`request`), split out so it lives with
/// hivectl rather than in the daemon crate.
mod client;
/// Rebuild-queue node progress rendering (`wait_for_nodes` + the spinner /
/// plain renderers), split out to keep this file manageable.
mod dag_progress;
use cli::{Cli, Cmd, ForgeCmd, GatewayCmd, WgCmd};
mod completions;
mod quota;
mod util;
use completions::generate_completions;
mod gateway;
use gateway::{gateway_create_user, gateway_delete_user, gateway_list_users};
mod matrix;
use matrix::run_matrix_cmd;
mod open;
use open::open_url;
mod wg;
use wg::{peer_config, require_hive_domain, wg_init, wg_peer, wg_status};
mod choom;
mod forge;
mod watch;
use forge::forge_reconcile_config;
mod agents;
use agents::{agents_list, run_agent};
mod power;
use power::{restart, start, stop};
mod subvol;
#[tokio::main]
async fn main() -> Result<()> {
tracing_subscriber::fmt()
.with_env_filter(
tracing_subscriber::EnvFilter::try_from_default_env()
.unwrap_or_else(|_| tracing_subscriber::EnvFilter::new("info")),
)
.init();
let cli = Cli::parse();
let socket = cli.socket;
match cli.cmd {
Cmd::Forge { cmd } => match cmd {
ForgeCmd::ReconcileConfig {
agent,
from,
verbose,
} => forge_reconcile_config(&socket, &agent, from, verbose).await,
},
Cmd::Matrix { cmd } => run_matrix_cmd(&socket, cmd).await,
Cmd::Gateway { cmd } => match cmd {
GatewayCmd::CreateUser {
username,
password,
password_stdin,
} => gateway_create_user(&socket, &username, password.as_deref(), password_stdin).await,
GatewayCmd::DeleteUser { username } => gateway_delete_user(&socket, &username).await,
GatewayCmd::ListUsers => gateway_list_users(&socket).await,
},
Cmd::Agent { name, cmd } => run_agent(&socket, &name, cmd).await,
Cmd::ListAgents { json } => agents_list(&socket, json).await,
Cmd::QuotaEnable => quota::quota_enable(&socket).await,
Cmd::Wg { cmd } => match cmd {
WgCmd::Init { address } => wg_init(&socket, address.as_deref()).await,
WgCmd::Peer {
domain,
pubkey,
address,
endpoint,
} => {
wg_peer(&domain, &pubkey, &address, endpoint.as_deref());
Ok(())
}
WgCmd::Status => wg_status(),
},
Cmd::PeerConfig {
wg_address,
wg_endpoint,
} => {
let domain = require_hive_domain(&socket).await?;
peer_config(&domain, wg_address.as_deref(), wg_endpoint.as_deref());
Ok(())
}
Cmd::Stop {
scope,
graceful,
no_wait,
} => stop(&socket, scope.to_scope(), graceful, no_wait).await,
Cmd::Start { scope, no_wait } => start(&socket, scope.to_scope(), no_wait).await,
Cmd::Restart { scope, graceful } => restart(&socket, scope.to_scope(), graceful).await,
Cmd::MarkdownDocs => {
// `show_footer(false)`: drop clap-markdown's own fixed
// "This document was generated automatically by..." footer —
// it's the one string in this doc that isn't ours to reword.
let options = clap_markdown::MarkdownOptions::new().show_footer(false);
print!("{}", clap_markdown::help_markdown_custom::<Cli>(&options));
Ok(())
}
Cmd::Open { target } => open_url(&socket, target).await,
Cmd::Completions { shell } => {
generate_completions(shell);
Ok(())
}
}
}