login: default command is claude auth login

This commit is contained in:
müde 2026-05-15 13:32:51 +02:00
parent e777576528
commit d81a845dbe
6 changed files with 10 additions and 10 deletions

View file

@ -271,7 +271,7 @@ See PLAN.md → "Phase 8" for the full design. Summary:
login` badge in the container list. "Valid session" today is a heuristic
(any regular file inside `/root/.claude/`); we may refine once the
filename layout claude writes is locked in.
- **Login from the per-agent web UI.** Spawn `claude /login` with plain
- **Login from the per-agent web UI.** Spawn `claude auth login` with plain
stdio pipes (no PTY initially), surface the OAuth URL from stdout on the
page, accept the resulting code via a paste field, write it to the process
stdin. Once `~/.claude/` populates, the existing needs-login polling loop

View file

@ -263,7 +263,7 @@ knows where to click.
**Login over the per-agent web UI.** No more `nixos-container root-login` for
the common case. The agent's web UI exposes a "log in" action that:
1. Spawns `claude /login` (or equivalent) inside the container with plain
1. Spawns `claude auth login` (or equivalent) inside the container with plain
stdio pipes — no PTY unless we discover we need one.
2. Reads the OAuth URL from the process stdout and shows it on the page.
3. Provides a paste field for the resulting code; writes it to the process

View file

@ -71,7 +71,7 @@ async fn main() -> Result<()> {
// stays bound) but don't drive the turn loop. Poll the
// claude dir periodically so a successful login (whether
// from the dashboard PTY path in step 4 or via
// `root-login` + `claude /login` in the meantime)
// `root-login` + `claude auth login` in the meantime)
// transitions us into the turn loop without a restart.
needs_login_loop(&cli.socket, &claude_dir, login_state, poll_ms).await
}

View file

@ -3,7 +3,7 @@
//! destroy/recreate so OAuth tokens survive.
//!
//! "Has session" today means "the dir contains at least one regular file."
//! That's a heuristic: a fresh bind-mount starts empty, and `claude /login`
//! That's a heuristic: a fresh bind-mount starts empty, and `claude auth login`
//! writes credentials into the dir. We may refine later (probe for the
//! specific credentials filename, or run a no-op `claude` call) once the
//! exact layout is locked in.

View file

@ -1,4 +1,4 @@
//! `claude /login` driver. Spawns the login command under plain stdio pipes,
//! `claude auth login` driver. Spawns the login command under plain stdio pipes,
//! accumulates stdout+stderr in a shared buffer (so the web UI can show
//! whatever URL/prompt claude emits), and writes paste-back codes from the
//! UI into the child's stdin.
@ -16,7 +16,7 @@ use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader};
use tokio::process::{Child, ChildStdin, Command};
const DEFAULT_CMD: &str = "claude";
const DEFAULT_ARGS: &[&str] = &["/login"];
const DEFAULT_ARGS: &[&str] = &["auth", "login"];
#[derive(Default)]
struct State {
@ -33,7 +33,7 @@ struct State {
exit_note: Option<String>,
}
/// A running `claude /login` subprocess.
/// A running `claude auth login` subprocess.
pub struct LoginSession {
child: Mutex<Child>,
/// Tokio mutex because we hold the guard across the `write_all().await`
@ -46,7 +46,7 @@ pub struct LoginSession {
impl LoginSession {
/// Spawn the login command. The exact binary/args are configurable via
/// `HYPERHIVE_LOGIN_CMD` (single string, shell-split into argv); by
/// default we run `claude /login`. Failing to spawn returns an error
/// default we run `claude auth login`. Failing to spawn returns an error
/// before any state is registered.
pub fn start() -> Result<Self> {
let (cmd, args) = resolve_command();
@ -146,7 +146,7 @@ impl LoginSession {
fn resolve_command() -> (String, Vec<String>) {
if let Ok(raw) = std::env::var("HYPERHIVE_LOGIN_CMD") {
// Whitespace-only split — no quote handling. Fine for "claude /login"
// Whitespace-only split — no quote handling. Fine for "claude auth login"
// style overrides; if we need anything with embedded spaces we'll
// switch to shell-words.
let mut parts = raw.split_whitespace().map(str::to_owned);

View file

@ -71,7 +71,7 @@ fn render_online() -> String {
}
fn render_needs_login_idle() -> String {
"<p class=\"status-needs-login\">▓█▓▒░ NEEDS L0G1N ▓█▓▒░</p>\n<p>No Claude session in <code>~/.claude/</code>. The harness is up but the turn loop is paused until you log in.</p>\n<form method=\"POST\" action=\"/login/start\">\n <button type=\"submit\" class=\"btn btn-login\">◆ ST4RT L0G1N</button>\n</form>\n<p class=\"meta\">Spawns <code>claude /login</code> over plain stdio pipes. The OAuth URL will appear here when claude emits it; paste the resulting code back into the form below.</p>".into()
"<p class=\"status-needs-login\">▓█▓▒░ NEEDS L0G1N ▓█▓▒░</p>\n<p>No Claude session in <code>~/.claude/</code>. The harness is up but the turn loop is paused until you log in.</p>\n<form method=\"POST\" action=\"/login/start\">\n <button type=\"submit\" class=\"btn btn-login\">◆ ST4RT L0G1N</button>\n</form>\n<p class=\"meta\">Spawns <code>claude auth login</code> over plain stdio pipes. The OAuth URL will appear here when claude emits it; paste the resulting code back into the form below.</p>".into()
}
fn render_login_in_progress(session: &Arc<LoginSession>) -> String {