feat(#1298): add hivectl choom verb — interactive claude session in agent container

This commit is contained in:
damocles 2026-06-04 19:47:23 +02:00 committed by mara
commit ab65245619

View file

@ -15,6 +15,8 @@
//! `hive-c0re` lib — single source of truth, no duplication.
use std::path::{Path, PathBuf};
#[cfg(unix)]
use std::os::unix::process::CommandExt as _;
use anyhow::{Context as _, Result, bail};
use clap::{Parser, Subcommand};
@ -69,6 +71,25 @@ enum Cmd {
#[command(subcommand)]
cmd: AgentsCmd,
},
/// Open an interactive Claude session inside an agent container.
///
/// Replaces the current process with `machinectl shell h-<name>`
/// running `claude --continue` — drops the operator straight into
/// the agent's live Claude session with its full loaded context and
/// persona. Requires root (same as all machinectl shell operations)
/// and the container must be running.
///
/// Pass `--fresh` to start a new Claude session instead of continuing
/// the most recent one.
Choom {
/// Agent name (e.g. `damocles`, `iris`).
name: String,
/// Start a fresh Claude session instead of continuing the most
/// recent one. Without this flag `--continue` is passed to Claude
/// so the operator joins the agent's ongoing session context.
#[arg(long)]
fresh: bool,
},
}
#[derive(Subcommand)]
@ -292,6 +313,7 @@ async fn main() -> Result<()> {
AgentsCmd::Restart { name, socket } => agents_restart(&socket, &name).await,
AgentsCmd::RestartAll { socket } => agents_restart_all(&socket).await,
},
Cmd::Choom { name, fresh } => choom(&name, fresh),
}
}
@ -304,6 +326,35 @@ fn is_agent(name: &str) -> bool {
Coordinator::agent_state_root(name).exists()
}
/// Drop into an interactive Claude session in the agent container.
///
/// Replaces the current process (exec) with `machinectl shell h-<name>
/// /run/current-system/sw/bin/claude [--continue]`. The `--continue`
/// flag is passed by default so the operator joins the agent's active
/// session; omit it via `--fresh` to start a blank session instead.
///
/// `machinectl shell` inherits the caller's PTY, so the session is
/// fully interactive. Requires root and a running container.
fn choom(name: &str, fresh: bool) -> Result<()> {
if !is_agent(name) {
bail!(
"no such agent: '{name}' (no state dir under /var/lib/hyperhive/agents/)"
);
}
let container = hive_c0re::lifecycle::container_name(name);
let claude = "/run/current-system/sw/bin/claude";
let mut cmd = std::process::Command::new("machinectl");
cmd.arg("shell").arg(&container).arg(claude);
if !fresh {
cmd.arg("--continue");
}
// exec() replaces the current process — we inherit stdin/stdout/stderr
// (the caller's PTY) so the Claude session is fully interactive.
// This call only returns on error.
let err = cmd.exec();
Err(anyhow::anyhow!("exec machinectl: {err}"))
}
async fn forge_create_user(name: &str, password: Option<&str>, password_stdin: bool) -> Result<()> {
if !hive_c0re::forge::is_present().await {
bail!(