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 login` badge in the container list. "Valid session" today is a heuristic
(any regular file inside `/root/.claude/`); we may refine once the (any regular file inside `/root/.claude/`); we may refine once the
filename layout claude writes is locked in. 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 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 page, accept the resulting code via a paste field, write it to the process
stdin. Once `~/.claude/` populates, the existing needs-login polling loop 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 **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: 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. 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. 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 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 // stays bound) but don't drive the turn loop. Poll the
// claude dir periodically so a successful login (whether // claude dir periodically so a successful login (whether
// from the dashboard PTY path in step 4 or via // 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. // transitions us into the turn loop without a restart.
needs_login_loop(&cli.socket, &claude_dir, login_state, poll_ms).await needs_login_loop(&cli.socket, &claude_dir, login_state, poll_ms).await
} }

View file

@ -3,7 +3,7 @@
//! destroy/recreate so OAuth tokens survive. //! destroy/recreate so OAuth tokens survive.
//! //!
//! "Has session" today means "the dir contains at least one regular file." //! "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 //! writes credentials into the dir. We may refine later (probe for the
//! specific credentials filename, or run a no-op `claude` call) once the //! specific credentials filename, or run a no-op `claude` call) once the
//! exact layout is locked in. //! 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 //! 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 //! whatever URL/prompt claude emits), and writes paste-back codes from the
//! UI into the child's stdin. //! UI into the child's stdin.
@ -16,7 +16,7 @@ use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader};
use tokio::process::{Child, ChildStdin, Command}; use tokio::process::{Child, ChildStdin, Command};
const DEFAULT_CMD: &str = "claude"; const DEFAULT_CMD: &str = "claude";
const DEFAULT_ARGS: &[&str] = &["/login"]; const DEFAULT_ARGS: &[&str] = &["auth", "login"];
#[derive(Default)] #[derive(Default)]
struct State { struct State {
@ -33,7 +33,7 @@ struct State {
exit_note: Option<String>, exit_note: Option<String>,
} }
/// A running `claude /login` subprocess. /// A running `claude auth login` subprocess.
pub struct LoginSession { pub struct LoginSession {
child: Mutex<Child>, child: Mutex<Child>,
/// Tokio mutex because we hold the guard across the `write_all().await` /// Tokio mutex because we hold the guard across the `write_all().await`
@ -46,7 +46,7 @@ pub struct LoginSession {
impl LoginSession { impl LoginSession {
/// Spawn the login command. The exact binary/args are configurable via /// Spawn the login command. The exact binary/args are configurable via
/// `HYPERHIVE_LOGIN_CMD` (single string, shell-split into argv); by /// `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. /// before any state is registered.
pub fn start() -> Result<Self> { pub fn start() -> Result<Self> {
let (cmd, args) = resolve_command(); let (cmd, args) = resolve_command();
@ -146,7 +146,7 @@ impl LoginSession {
fn resolve_command() -> (String, Vec<String>) { fn resolve_command() -> (String, Vec<String>) {
if let Ok(raw) = std::env::var("HYPERHIVE_LOGIN_CMD") { 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 // style overrides; if we need anything with embedded spaces we'll
// switch to shell-words. // switch to shell-words.
let mut parts = raw.split_whitespace().map(str::to_owned); 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 { 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 { fn render_login_in_progress(session: &Arc<LoginSession>) -> String {