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);
|
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;
|
overflowMenuPopulated = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -363,6 +391,7 @@ window.marked = marked;
|
||||||
{ name: '/compact', desc: 'compact the persistent claude session' },
|
{ name: '/compact', desc: 'compact the persistent claude session' },
|
||||||
{ name: '/model', desc: '/model <name> — switch claude model for future turns' },
|
{ name: '/model', desc: '/model <name> — switch claude model for future turns' },
|
||||||
{ name: '/new-session', desc: 'next turn runs without --continue (fresh claude session)' },
|
{ 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) {
|
async function postModel(name) {
|
||||||
|
|
@ -402,6 +431,7 @@ window.marked = marked;
|
||||||
const postCancelTurn = () => postSimple('/api/cancel', '/cancel');
|
const postCancelTurn = () => postSimple('/api/cancel', '/cancel');
|
||||||
const postCompact = () => postSimple('/api/compact', '/compact');
|
const postCompact = () => postSimple('/api/compact', '/compact');
|
||||||
const postNewSession = () => postSimple('/api/new-session', '/new-session');
|
const postNewSession = () => postSimple('/api/new-session', '/new-session');
|
||||||
|
const postLogout = () => postSimple('/api/logout', '/logout');
|
||||||
|
|
||||||
function handleSlashCommand(line) {
|
function handleSlashCommand(line) {
|
||||||
if (!termAPI) return false;
|
if (!termAPI) return false;
|
||||||
|
|
@ -430,6 +460,15 @@ window.marked = marked;
|
||||||
postNewSession();
|
postNewSession();
|
||||||
}
|
}
|
||||||
return true;
|
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': {
|
case '/model': {
|
||||||
const parts = trimmed.split(/\s+/);
|
const parts = trimmed.split(/\s+/);
|
||||||
if (parts.length < 2 || !parts[1]) {
|
if (parts.length < 2 || !parts[1]) {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue