agent: 🔓 logout in overflow menu + /logout slash command (#576 frontend half)
Pairs with damocles PR #582 (POST /api/logout backend on the per-agent web UI). Three additions to the agent's app.js: - New `postLogout` helper next to postCancelTurn / postCompact / postNewSession. Same postSimple shape. - New entry in SLASH_COMMANDS so /help lists /logout. - New /logout case in handleSlashCommand with window.confirm. - New '🔓 logout' item in populateOverflowMenu's overflow popover, mirroring the new-session item's pattern (confirm before POST, disable button while in-flight, closeOverflowMenu before fire). Confirm dialog spells out the consequences (SIGINT, creds wiped, park in needs-login) so the operator doesn't accidentally drop a production session. Tooltip on the menu item links the action back to the credentials directory + post-logout state. Wire-level: POST /api/logout, no body. Backend returns 200 with a text body describing the wipe outcome — postSimple ignores it (success → no terminal note; failure → red turn-end-fail row).
This commit is contained in:
parent
3526991385
commit
259b236ad1
1 changed files with 39 additions and 0 deletions
|
|
@ -218,6 +218,34 @@ 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/ credentials + 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, wipes the credentials directory, ` +
|
||||
`and parks the agent in 'needs login' until you paste a fresh OAuth code from the login screen. ` +
|
||||
`prior --continue context is not affected (only the OAuth creds).`
|
||||
)) return;
|
||||
logoutBtn.disabled = true;
|
||||
closeOverflowMenu();
|
||||
postLogout().finally(() => { logoutBtn.disabled = false; });
|
||||
});
|
||||
menu.append(logoutBtn);
|
||||
|
||||
overflowMenuPopulated = true;
|
||||
}
|
||||
|
||||
|
|
@ -363,6 +391,7 @@ window.marked = marked;
|
|||
{ name: '/compact', desc: 'compact the persistent claude session' },
|
||||
{ name: '/model', desc: '/model <name> — switch claude model for future turns' },
|
||||
{ name: '/new-session', desc: 'next turn runs without --continue (fresh claude session)' },
|
||||
{ name: '/logout', desc: 'wipe ~/.claude/ credentials + park in needs-login' },
|
||||
];
|
||||
|
||||
async function postModel(name) {
|
||||
|
|
@ -402,6 +431,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 +460,15 @@ window.marked = marked;
|
|||
postNewSession();
|
||||
}
|
||||
return true;
|
||||
case '/logout':
|
||||
if (window.confirm(
|
||||
`log out? this SIGINTs any running claude turn, wipes the credentials directory, ` +
|
||||
`and parks the agent in 'needs login' until you paste a fresh OAuth code from the login screen. ` +
|
||||
`prior --continue context is not affected (only the OAuth creds).`
|
||||
)) {
|
||||
postLogout();
|
||||
}
|
||||
return true;
|
||||
case '/model': {
|
||||
const parts = trimmed.split(/\s+/);
|
||||
if (parts.length < 2 || !parts[1]) {
|
||||
|
|
|
|||
Loading…
Reference in a new issue