feat: 'Show CLI sessions' toggle in Settings (#61)

Adds a server-side boolean setting (default: false) that controls whether
CLI sessions from state.db appear in the sidebar. Off by default so the
sidebar is clean until the user explicitly opts in.

- api/config.py: add show_cli_sessions to _SETTINGS_DEFAULTS and _SETTINGS_BOOL_KEYS
- api/routes.py: gate get_cli_sessions() call on the setting at request time
- static/index.html: checkbox in settings panel with description
- static/panels.js: load/save checkbox, refresh session list on save
- static/boot.js: load on startup alongside send_key and show_token_usage

Co-authored-by: Nathan Esquenazi <nesquena@gmail.com>
This commit is contained in:
nesquena-hermes
2026-04-03 21:06:23 -07:00
committed by GitHub
parent 1a4d56c215
commit 66f95e08c2
5 changed files with 23 additions and 6 deletions

View File

@@ -633,6 +633,7 @@ _SETTINGS_DEFAULTS = {
'default_workspace': str(DEFAULT_WORKSPACE),
'send_key': 'enter', # 'enter' or 'ctrl+enter'
'show_token_usage': False, # show input/output token badge below assistant messages
'show_cli_sessions': False, # merge CLI sessions from state.db into the sidebar
'password_hash': None, # SHA-256 hash; None = auth disabled
}
@@ -652,7 +653,7 @@ _SETTINGS_ALLOWED_KEYS = set(_SETTINGS_DEFAULTS.keys()) - {'password_hash'}
_SETTINGS_ENUM_VALUES = {
'send_key': {'enter', 'ctrl+enter'},
}
_SETTINGS_BOOL_KEYS = {'show_token_usage'}
_SETTINGS_BOOL_KEYS = {'show_token_usage', 'show_cli_sessions'}
def save_settings(settings: dict) -> dict:
"""Save settings to disk. Returns the merged settings. Ignores unknown keys."""

View File

@@ -188,10 +188,13 @@ def handle_get(handler, parsed):
if parsed.path == '/api/sessions':
webui_sessions = all_sessions()
settings = load_settings()
if settings.get('show_cli_sessions'):
cli = get_cli_sessions()
# Deduplicate: WebUI sessions always win if same session_id
webui_ids = {s['session_id'] for s in webui_sessions}
deduped_cli = [s for s in cli if s['session_id'] not in webui_ids]
else:
deduped_cli = []
merged = webui_sessions + deduped_cli
merged.sort(key=lambda s: s.get('updated_at', 0) or 0, reverse=True)
return j(handler, {'sessions': merged, 'cli_count': len(deduped_cli)})

View File

@@ -308,7 +308,7 @@ document.querySelectorAll('.suggestion').forEach(btn=>{
(async()=>{
// Load send key preference
try{const s=await api('/api/settings');window._sendKey=s.send_key||'enter';window._showTokenUsage=!!s.show_token_usage;}catch(e){window._sendKey='enter';window._showTokenUsage=false;}
try{const s=await api('/api/settings');window._sendKey=s.send_key||'enter';window._showTokenUsage=!!s.show_token_usage;window._showCliSessions=!!s.show_cli_sessions;}catch(e){window._sendKey='enter';window._showTokenUsage=false;window._showCliSessions=false;}
// Fetch active profile
try{const p=await api('/api/profile/active');S.activeProfile=p.name||'default';}catch(e){S.activeProfile='default';}
// Update profile chip label immediately

View File

@@ -331,6 +331,13 @@
</label>
<div style="font-size:11px;color:var(--muted);margin-top:4px">Displays input/output token count below each assistant reply. Also toggled with <code>/usage</code>.</div>
</div>
<div class="settings-field">
<label style="display:flex;align-items:center;gap:8px;cursor:pointer">
<input type="checkbox" id="settingsShowCliSessions" style="width:15px;height:15px;accent-color:var(--accent)">
Show CLI sessions in sidebar
</label>
<div style="font-size:11px;color:var(--muted);margin-top:4px">Merges sessions from the Hermes CLI (state.db) into the session list. Click a CLI session to import it and continue the conversation.</div>
</div>
<div class="settings-field" style="border-top:1px solid var(--border);padding-top:12px;margin-top:8px">
<label for="settingsPassword">Access Password</label>
<div style="font-size:11px;color:var(--muted);margin-bottom:6px">Enter a new password to set or change it. Leave blank to keep current setting.</div>

View File

@@ -960,6 +960,8 @@ async function loadSettingsPanel(){
if(sendKeySel) sendKeySel.value=settings.send_key||'enter';
const showUsageCb=$('settingsShowTokenUsage');
if(showUsageCb) showUsageCb.checked=!!settings.show_token_usage;
const showCliCb=$('settingsShowCliSessions');
if(showCliCb) showCliCb.checked=!!settings.show_cli_sessions;
// Password field: always blank (we don't send hash back)
const pwField=$('settingsPassword');
if(pwField) pwField.value='';
@@ -982,12 +984,14 @@ async function saveSettings(){
const workspace=($('settingsWorkspace')||{}).value;
const sendKey=($('settingsSendKey')||{}).value;
const showTokenUsage=!!($('settingsShowTokenUsage')||{}).checked;
const showCliSessions=!!($('settingsShowCliSessions')||{}).checked;
const pw=($('settingsPassword')||{}).value;
const body={};
if(model) body.default_model=model;
if(workspace) body.default_workspace=workspace;
if(sendKey) body.send_key=sendKey;
body.show_token_usage=showTokenUsage;
body.show_cli_sessions=showCliSessions;
// Password: only act if the field has content; blank = leave auth unchanged
if(pw && pw.trim()){
try{
@@ -1003,7 +1007,9 @@ async function saveSettings(){
await api('/api/settings',{method:'POST',body:JSON.stringify(body)});
window._sendKey=sendKey||'enter';
window._showTokenUsage=showTokenUsage;
window._showCliSessions=showCliSessions;
renderMessages();
if(typeof renderSessionList==='function') renderSessionList();
showToast('Settings saved');
toggleSettings();
}catch(e){