//! hivectl clap command tree: the `Cli` root, the top-level `Cmd` //! verb enum, and every subcommand enum + shared args struct. use clap::{Args, Parser, Subcommand}; use std::path::PathBuf; #[derive(Parser)] #[command( name = "hivectl", about = "hyperhive host CLI — operator-facing administration", long_about = "\ Sibling to the `hive-c0re` daemon binary. Covers host-side admin \ operations that don't go through the broker — manual user \ provisioning on the bundled forge + matrix containers, plus future \ recovery / debugging verbs.\ " )] pub struct Cli { /// Path to the hive-c0re host admin socket, used by the daemon-assisted /// verbs (`agent`, `list-agents`, `stop`, `start`). Global: accepted /// before or after the subcommand. Verbs that don't talk to the daemon /// ignore it. #[arg(long, global = true, default_value = DEFAULT_HOST_SOCKET)] pub(crate) socket: PathBuf, #[command(subcommand)] pub(crate) cmd: Cmd, } #[derive(Subcommand)] pub enum Cmd { /// Forgejo user provisioning. /// /// Manual entry point to the same idempotent provisioning c0re runs at /// boot — for recovery, ad-hoc reprovisioning, or fixing one agent /// without bouncing the daemon. Forge { #[command(subcommand)] cmd: ForgeCmd, }, /// matrix-tuwunel user provisioning. /// /// Manual entry point to the same idempotent provisioning c0re runs at /// boot — for re-registering an agent the boot sweep skipped, or after /// wiping a token file. Matrix { #[command(subcommand)] cmd: MatrixCmd, }, /// GitHub account provisioning. /// /// Store an operator-supplied personal access token (PAT) for an agent /// so its `gh` and git can authenticate. No account is created — the /// PAT is for an existing GitHub account. Github { #[command(subcommand)] cmd: GithubCmd, }, /// Gateway htpasswd user management. /// /// Add, remove, or list users for the gateway's HTTP Basic auth. Gateway { #[command(subcommand)] cmd: GatewayCmd, }, /// Lifecycle actions on ONE managed agent container. Needs the /// hive-c0re daemon running. /// /// Everything here targets a single named agent (`hivectl agent foo /// restart`, `hivectl agent foo choom`, …) — anything that acts /// hive-wide lives at the top level instead (`list-agents`, /// `restart`/`stop`/`start` with a scope, `quota-enable`). Agent { /// Agent name (e.g. `damocles`, `iris`). name: String, #[command(subcommand)] cmd: AgentCmd, }, /// Show all managed agents with their status and technical state. /// /// Global — not scoped to one agent, so it lives at the top level /// rather than under `hivectl agent `. Needs the hive-c0re /// daemon running. ListAgents { /// Emit the raw JSON rows instead of the padded table (for /// scripting). The table is the default human-readable shape. #[arg(long)] json: bool, }, /// Enable btrfs qgroup accounting on the agent-state filesystem. /// /// Global one-shot toggle (not per-agent), hence top-level rather than /// under `hivectl agent `. Idempotent — safe to re-run. Once /// enabled, `hivectl agent quota show`/`quota set` work. QuotaEnable, /// Operator approval queue: list, approve, or deny pending requests. /// /// Needs the hive-c0re daemon running. Approvals { #[command(subcommand)] cmd: ApprovalsCmd, }, /// WireGuard inter-hive mesh setup helpers. /// /// Generate this hive's mesh key and print the nix to enable the mesh, /// add a peer, or inspect live interface state. Wg { #[command(subcommand)] cmd: WgCmd, }, /// Generate the federation peer-config block for THIS hive. /// /// Prints the nix a peer operator pastes into their swarm config to /// trust and reach this hive. PeerConfig { /// This hive's WireGuard mesh address (e.g. `10.42.0.1/32`), /// emitted as `wireguardAddress`. Omit when not running the mesh. #[arg(long)] wg_address: Option, /// This hive's public WireGuard endpoint (`host:port`), emitted as /// `wireguardEndpoint`. Omit when peers dial in / no mesh. #[arg(long)] wg_endpoint: Option, }, /// Stop containers hive-wide in one operator action. /// /// Bare `hivectl stop` stops everything; scope flags narrow it to /// specific sub-agents or infra containers. Stop { #[command(flatten)] scope: ScopeArgs, /// Gracefully quiesce each agent before stopping, instead of a /// hard stop. Each agent gets a graceful-stop DAG on the job /// queue: the harness is signalled, runs one stop-checkpoint /// turn to flush durable `/state`, drains, then the container /// is stopped (bounded by a 3-min timeout that falls back to a /// hard stop). All drains overlap. Applies to agents only. #[arg(long)] graceful: bool, /// Return immediately after the stop DAGs are queued instead of /// waiting for them with live per-node progress. #[arg(long)] no_wait: bool, }, /// Start containers hive-wide — the inverse of `hivectl stop`. /// /// Bare `hivectl start` restores the agents stopped by the last /// broad-scope `stop` (or starts everything if none); scope flags /// narrow it. Start { #[command(flatten)] scope: ScopeArgs, /// Return immediately after the start DAGs are queued instead /// of waiting for them with live per-node progress. #[arg(long)] no_wait: bool, }, /// Restart containers hive-wide — `stop` then `start` over one scope. /// /// Bare `hivectl restart` restarts everything; scope flags narrow it. Restart { #[command(flatten)] scope: ScopeArgs, /// Gracefully quiesce each agent on the stop half (see /// `stop --graceful`). Applies to agents only. #[arg(long)] graceful: bool, }, /// Print (and best-effort open in a browser) a hive web surface URL. /// /// Resolves the URL from the running daemon so custom forge / matrix /// domains work. Bare `hivectl open` opens the operator dashboard. Open { /// Which surface to open. Defaults to the operator dashboard. #[arg(value_enum, default_value_t = OpenTarget::Home)] target: OpenTarget, }, /// Emit the full CLI reference as `CommonMark` to stdout. /// /// Hidden tooling command used by the docs build to keep the published /// `hivectl` reference in lockstep with the code. #[command(hide = true)] MarkdownDocs, /// Generate a shell completion script for `hivectl` and print it to /// stdout. /// /// Supports bash, zsh, fish, elvish, and powershell. The NixOS module /// already installs the zsh script system-wide; this is for ad-hoc or /// other-shell use. Completions { /// Shell to emit completions for. shell: clap_complete::Shell, }, } /// Which hive web surface `hivectl open` targets. #[derive(Copy, Clone, Debug, clap::ValueEnum)] pub enum OpenTarget { /// The operator dashboard (`https:///`). Home, /// The forge (Forgejo) web UI. Forge, /// The matrix GUI (fluffychat). Matrix, } /// Shared scope flags for `hivectl stop` / `hivectl start`. With no flag /// set the verb targets **everything** (all sub-agents + every controllable /// infra container). Setting any flag restricts to the selected classes, /// additively. // One bool per selectable container class, each mapping 1:1 to a clap flag; // orthogonal toggles, not a state machine — hence the bools allow (mirrors // `hive_host_sock::LifecycleScope`). #[allow(clippy::struct_excessive_bools)] #[derive(Args)] pub struct ScopeArgs { /// All sub-agent containers. #[arg(long)] agents: bool, /// A specific sub-agent by name. Repeatable: `--agent a --agent b`. #[arg(long = "agent", value_name = "NAME")] agent: Vec, /// The CI runner container (`hive-ci`). #[arg(long)] ci: bool, /// The forge container (`hive-forge`). #[arg(long)] forge: bool, /// The gateway container (`hive-gateway`). #[arg(long)] gateway: bool, /// The matrix container (`hive-matrix`). #[arg(long)] matrix: bool, } impl ScopeArgs { pub(crate) fn to_scope(&self) -> hive_host_sock::LifecycleScope { hive_host_sock::LifecycleScope { agents: self.agents, agent_names: self.agent.clone(), ci: self.ci, forge: self.forge, gateway: self.gateway, matrix: self.matrix, } } } #[derive(Subcommand)] pub enum ForgeCmd { /// Create or refresh the Forgejo account + token for ``. /// /// For an existing agent, persists the token to its state dir; for a /// human/other account, prints the token to stdout. Set a password to /// enable forge web-UI login (a random throwaway is used otherwise). CreateUser { /// Forgejo username. For agents: the container/agent name /// (`` in `h-`; manager uses the literal `manager`). /// For humans: any forgejo username — `mara`, `damocles`, etc. name: String, /// Set the account password to this string instead of a random /// throwaway. Use this for operator accounts that need to log /// into the forge web UI. Mutually exclusive with /// `--password-stdin`. WARNING: the password is visible in /// shell history + process listings; prefer `--password-stdin` /// for anything sensitive. #[arg(long)] password: Option, /// Read the password from stdin (single line, trailing newline /// stripped) instead of an inline flag. Mutually exclusive with /// `--password`. #[arg(long, conflicts_with = "password")] password_stdin: bool, }, /// Show + reconcile the divergence between an agent's local applied /// config checkout and its forge `agent-configs/` main. /// /// Always prints the diff first. `--from forge` resets the local /// checkout to forge main (effective on the next deploy); `--from local` /// is not supported yet. With no `--from`, prompts for the direction. ReconcileConfig { /// Agent whose config branches to reconcile. agent: String, /// Which side to reconcile from. Omit to be prompted after the diff. #[arg(long, value_enum)] from: Option, /// Include the full diff (not just `--stat`) in the report. #[arg(long)] verbose: bool, }, } /// Which side to reconcile config branches from (`hivectl forge /// reconcile-config --from`). Maps to [`hive_host_sock::ReconcileDirection`]. #[derive(Clone, Copy, clap::ValueEnum)] pub enum ReconcileFrom { /// Reset the local applied checkout to forge main. Forge, /// Advance forge main from local — not supported yet. Local, } impl From for hive_host_sock::ReconcileDirection { fn from(from: ReconcileFrom) -> Self { match from { ReconcileFrom::Forge => Self::Forge, ReconcileFrom::Local => Self::Local, } } } #[derive(Subcommand)] pub enum MatrixCmd { /// Create or refresh the matrix account + access token for ``. /// /// For an existing agent, persists the token to its state dir; for a /// human/other account, prints the access token to stdout. Set a /// password to enable matrix web-client login (a random throwaway is /// used otherwise). CreateUser { /// Matrix localpart. For agents: the container/agent name. /// For humans: any matrix localpart — `mara`, `damocles`, etc. name: String, /// Set the account password to this string instead of a random /// throwaway. Use this for operator accounts that need to log /// into matrix web clients via `m.login.password`. Mutually /// exclusive with `--password-stdin`. WARNING: the /// password is visible in shell history + process listings; /// prefer `--password-stdin` for anything sensitive. #[arg(long)] password: Option, /// Read the password from stdin (single line, trailing newline /// stripped) instead of an inline flag. Mutually exclusive with /// `--password`. #[arg(long, conflicts_with = "password")] password_stdin: bool, }, /// Provision (or re-provision) the hive system admin matrix account. /// /// Runs automatically on startup; run manually to recover a missing /// admin token. SyncAdmin, /// Promote a matrix user to homeserver admin. PromoteUser { /// Matrix localpart of the user to promote (e.g. `argus`). name: String, }, /// Reset a matrix user's password via the admin API. /// /// Persists the new password so a later `create-user` can re-login. ResetPassword { /// Matrix localpart of the account to reset (e.g. `argus`). name: String, }, /// Invite a matrix user to the hive Space, or a specific room with /// `--room`. Idempotent. Invite { /// User to invite: a full id (`@mara:server`) or a bare /// localpart (qualified with the homeserver's `server_name`). user: String, /// Target room id (`!abc:server`) or alias (`#name:server`). /// Omit to invite to the hive Space. #[arg(long)] room: Option, }, } #[derive(Subcommand)] pub enum GithubCmd { /// Store a GitHub PAT for `` so its `gh` and git can /// authenticate. /// /// Prefer `--token-stdin` — an inline `--token` is visible in shell /// history. SetToken { /// Logical agent name (the container/agent name). agent: String, /// The PAT value inline. Mutually exclusive with `--token-stdin`. #[arg(long)] token: Option, /// Read the PAT from stdin (trailing newline stripped). Mutually /// exclusive with `--token`. #[arg(long, conflicts_with = "token")] token_stdin: bool, }, } #[derive(Subcommand)] pub enum GatewayCmd { /// Add a user or update an existing user's password in the gateway /// htpasswd. /// /// Use `--password-stdin` to keep the password out of shell history. CreateUser { /// Username to add or update. username: String, /// Set the password inline. WARNING: visible in shell history and /// process listings — prefer `--password-stdin` for sensitive input. /// Mutually exclusive with `--password-stdin`. #[arg(long, conflicts_with = "password_stdin")] password: Option, /// Read the password from stdin (single line, trailing newline /// stripped). Mutually exclusive with `--password`. #[arg(long)] password_stdin: bool, }, /// Remove a user from the gateway htpasswd. DeleteUser { /// Username to remove. username: String, }, /// List all gateway htpasswd usernames, one per line. ListUsers, } #[derive(Subcommand)] pub enum WgCmd { /// Generate this hive's WireGuard key (if absent) and print its public /// key plus the nix to enable the mesh. Init { /// This hive's mesh address (e.g. `10.42.0.1/32`) to bake into the /// printed snippet. Omit to get a placeholder you fill in. #[arg(long)] address: Option, }, /// Print the nix to add a peer hive to the mesh. Peer { /// Peer hive's DNS domain (the `swarm.peers` attrset key). domain: String, /// Peer's WireGuard public key (from its `hivectl wg init`). #[arg(long)] pubkey: String, /// Peer's mesh address (e.g. `10.42.0.2/32`). #[arg(long)] address: String, /// Peer's `host:port` endpoint (omit for a peer that only dials out, /// e.g. one behind NAT — it must set an endpoint pointing back here). #[arg(long)] endpoint: Option, }, /// Show the live mesh interface state. Status, } // Default host admin socket path. Shared with `hive-c0re`'s `main.rs` // default via `hive_host_sock::HOST_SOCKET` — the daemon binds there // and `hivectl` connects to it. pub(crate) use hive_host_sock::HOST_SOCKET as DEFAULT_HOST_SOCKET; /// Verbs under `hivectl agent ` — every one of these targets /// the single agent named on the parent command, so none of them carry /// their own `name` field. #[derive(Subcommand)] pub enum AgentCmd { /// Stop and start this agent container without rebuilding config. Restart { /// Return immediately after the restart DAG is queued. #[arg(long)] no_wait: bool, }, /// Park this agent's turn loop, leaving the container running. /// /// The harness stops driving turns but keeps serving its web UI and /// MCP daemons, so the container, its mounts and its warm caches stay /// up while it burns no tokens. Inbox messages queue unacked and the /// backlog drains on `resume`. Sticky: it survives a restart, and /// pausing a stopped agent makes it come up paused. Pause, /// Resume this paused agent — it drains whatever queued up while parked. Resume, /// Spawn this agent container directly, bypassing the approval queue. /// /// Operator-on-the-host only; use `request-spawn` for an approval-gated /// spawn. Spawn, /// Queue a spawn request for operator approval. RequestSpawn, /// Stop this managed container (graceful). Kill, /// Tear down this sub-agent container, keeping its state by default. /// No undo. Destroy { /// Also wipe the agent's state dirs (config + creds + notes). #[arg(long)] purge: bool, }, /// Apply pending config to this managed container. Rebuild, /// Move this agent in the topology tree — under a new parent, or to /// root. SetParent { /// New parent agent name. Mutually exclusive with `--root`. #[arg(long, conflicts_with = "root", required_unless_present = "root")] parent: Option, /// Promote this agent to root (no parent). #[arg(long)] root: bool, }, /// Declare this agent's CPU/memory limits, overriding the hive-wide /// defaults. /// /// Replaces the agent's whole override entry rather than merging into /// it: any limit you don't pass returns to the hive-wide default. To /// change one and keep the other, pass both. Disk is a separate /// resource with its own group — see `quota`. SetLimits { /// systemd `CPUQuota=` value, e.g. `400%` (100% = one full core). #[arg(long, conflicts_with = "reset")] cpu_quota: Option, /// systemd `MemoryMax=` value, e.g. `8G`, `50%`, or `infinity`. #[arg(long, conflicts_with = "reset")] memory_max: Option, /// Drop all overrides — the agent returns to the hive-wide /// defaults. Required to clear limits, so that a `set-limits` /// with a forgotten value can't silently reset the agent. #[arg( long, conflicts_with_all = ["cpu_quota", "memory_max"], required_unless_present_any = ["cpu_quota", "memory_max"], )] reset: bool, }, /// Open an interactive Claude session inside this agent's container. /// /// A fresh session by default, or resume a prior one. Requires root /// and a running container. Choom { /// Resume a prior claude session by its session id, passed /// through as `claude --resume ` (claude's `--continue` /// takes no value — it resumes the cwd's latest session, which /// is the harness's, so choom never uses it; this flag matches /// the claude flag it maps to). Omit for a fresh blank session. /// A value is required when the flag is given. #[arg(long = "resume", value_name = "SESSION")] resume_session: Option, }, /// Follow this agent's live turn/tool-call event stream from the CLI. /// /// Prints one compact line per event as they happen. `Ctrl-C` to stop. /// Requires the agent to be running. Watch, /// This agent's disk accounting + optional quota via btrfs qgroups. /// /// Needs `hivectl quota-enable` run once hive-wide first. No-op on /// non-btrfs hosts. Quota { #[command(subcommand)] cmd: AgentQuotaCmd, }, /// btrfs subvolume management for this agent's state dir. /// /// Upgrade an existing plain-dir agent's state into a btrfs subvolume /// so it gains snapshots and per-subvol usage/quota. Subvol { #[command(subcommand)] cmd: SubvolCmd, }, } #[derive(Subcommand)] pub enum AgentQuotaCmd { /// Report this agent's disk usage from btrfs qgroups. Show, /// Set or clear this agent's disk-usage quota. Set { /// Size cap (`5G`, `500M`, `1073741824`) or `none` to clear. size: String, }, } /// Operator approval queue: list, approve, or deny pending requests. #[derive(Subcommand)] pub enum ApprovalsCmd { /// List pending approval requests submitted by agents. Pending, /// Approve a pending request by id; the action runs immediately. Approve { /// Approval id (from `hivectl approvals pending`). id: i64, }, /// Deny a pending request by id. Deny { /// Approval id. id: i64, }, } #[derive(Subcommand)] pub enum SubvolCmd { /// Convert a plain-dir agent state root into a btrfs subvolume in /// place, so it gains snapshots and per-subvol usage/quota. /// /// Bounces the agent to migrate its state, so it requires `--yes`. Upgrade { /// Confirm: this stops the agent, migrates its state dir, and /// restarts it. Required — the command refuses without it. #[arg(long)] yes: bool, }, /// Read-only snapshots of this agent's state subvolume. Snapshot { #[command(subcommand)] cmd: SnapshotCmd, }, } #[derive(Subcommand)] pub enum SnapshotCmd { /// Create a read-only snapshot (agent must already be a subvolume). Create { /// Snapshot label. Mandatory, and must start with `hive-` — the /// prefix doubles as an allow-list hive-priv checks so only /// hivectl-issued snapshot names can reach the `btrfs subvolume /// snapshot` shellout. #[arg(long)] label: String, }, /// Delete a snapshot created by `subvol snapshot create`. Delete { /// Snapshot label passed to `subvol snapshot create --label`. label: String, }, /// Export a snapshot to a local file via `btrfs send` — the /// local-file half of the inter-hive migration transport (`push` /// is the network half). Also useful standalone as a point-in-time /// backup: a full send with no `--parent` produces a /// self-contained archive of the snapshot. Send { /// Snapshot label passed to `subvol snapshot create --label`. label: String, /// Optional parent snapshot label for an incremental send /// (`btrfs send -p`) — must be an existing, older snapshot of the /// same agent. Omit for a full send. #[arg(long)] parent: Option, /// Destination filename (not a path) under the migrate-staging /// dir. Refused if it already exists. #[arg(long)] dest: String, }, /// Stream a snapshot to the swarm's snapshot store over the /// WireGuard mesh — the network half of the migration transport. /// /// Nothing is staged locally: `btrfs send` writes straight into the /// connection, so a multi-gigabyte agent needs no scratch space on /// this host. The mesh is the authentication (cryptokey routing /// binds the sender's address to its key), so there is no credential /// to pass here. /// /// There is no destination argument: a swarm has one store, read /// from `services.hyperhive.swarm.snapshotStore`. Push { /// Snapshot label passed to `subvol snapshot create --label`. label: String, /// Optional parent snapshot label for an incremental send /// (`btrfs send -p`) — must be an existing, older snapshot of the /// same agent, and must already be present on the receiver. /// Omit for a full send. #[arg(long)] parent: Option, }, }