diff --git a/frontend/packages/agent/src/app.js b/frontend/packages/agent/src/app.js index 88e49215..a795fb01 100644 --- a/frontend/packages/agent/src/app.js +++ b/frontend/packages/agent/src/app.js @@ -218,6 +218,36 @@ window.marked = marked; }); menu.append(newSessBtn); + // 🔓 logout (#576) — SIGINTs claude, wipes the credentials dir, + // flips the harness LoginState to NeedsLogin. The turn loop's + // next iteration parks in wait_for_login; a fresh `claude auth + // login` from the dashboard re-arms it (#542 mtime resumption). + // Destructive — the operator has to re-paste OAuth creds on the + // login screen after — so we confirm with a clear-eyed prompt. + const logoutBtn = el('button', { + type: 'button', + class: 'overflow-item overflow-item-logout', + role: 'menuitem', + id: 'logout-btn', + title: 'wipe ~/.claude/ entirely (OAuth creds + projects/*.jsonl --continue history) + park in needs-login until a fresh `claude auth login` runs', + }, + el('span', { class: 'overflow-item-icon', 'aria-hidden': 'true' }, '🔓'), + 'logout', + ); + logoutBtn.addEventListener('click', () => { + if (!window.confirm( + `log ${label} out? this SIGINTs any running claude turn, then WIPES the entire ` + + `~/.claude/ directory — that includes OAuth credentials AND the projects/*.jsonl ` + + `session history (--continue context). the agent then parks in 'needs login' until ` + + `you paste a fresh OAuth code from the login screen. all prior conversation state ` + + `for this agent is lost. no undo.` + )) return; + logoutBtn.disabled = true; + closeOverflowMenu(); + postLogout().finally(() => { logoutBtn.disabled = false; }); + }); + menu.append(logoutBtn); + overflowMenuPopulated = true; } @@ -363,6 +393,7 @@ window.marked = marked; { name: '/compact', desc: 'compact the persistent claude session' }, { name: '/model', desc: '/model — switch claude model for future turns' }, { name: '/new-session', desc: 'next turn runs without --continue (fresh claude session)' }, + { name: '/logout', desc: 'wipe ~/.claude/ (creds + --continue history) + park in needs-login' }, ]; async function postModel(name) { @@ -402,6 +433,7 @@ window.marked = marked; const postCancelTurn = () => postSimple('/api/cancel', '/cancel'); const postCompact = () => postSimple('/api/compact', '/compact'); const postNewSession = () => postSimple('/api/new-session', '/new-session'); + const postLogout = () => postSimple('/api/logout', '/logout'); function handleSlashCommand(line) { if (!termAPI) return false; @@ -430,6 +462,17 @@ window.marked = marked; postNewSession(); } return true; + case '/logout': + if (window.confirm( + `log out? this SIGINTs any running claude turn, then WIPES the entire ` + + `~/.claude/ directory — that includes OAuth credentials AND the projects/*.jsonl ` + + `session history (--continue context). the agent then parks in 'needs login' until ` + + `you paste a fresh OAuth code from the login screen. all prior conversation state ` + + `for this agent is lost. no undo.` + )) { + postLogout(); + } + return true; case '/model': { const parts = trimmed.split(/\s+/); if (parts.length < 2 || !parts[1]) {