feat: add streamer mode for privacy during streaming
CI / Lint & Test (pull_request) Failing after 5m40s
CI / Build Linux (pull_request) Has been skipped
CI / Build Windows (cross-compile) (pull_request) Has been skipped
Security Scan and Upload / Security & DefectDojo Upload (pull_request) Successful in 49s

- Add quick toggle button in InputBar for easy access
- Mask API keys in settings when streamer mode active
- Optional path masking to hide usernames in file paths
- Visual LIVE indicator in both InputBar and StatusBar
- Keyboard shortcut Ctrl+Shift+S for quick toggle
- Privacy section in settings for additional options

Closes #35
This commit is contained in:
2026-01-25 18:15:47 -08:00
committed by Naomi Carrigan
parent 0ac52c8c8d
commit ff50b28641
7 changed files with 235 additions and 42 deletions
+93 -39
View File
@@ -27,6 +27,8 @@
update_checks_enabled: true,
character_panel_width: null,
font_size: 14,
streamer_mode: false,
streamer_hide_paths: false,
});
let isOpen = $state(false);
@@ -187,47 +189,60 @@
<div class="mb-4">
<label for="api-key" class="block text-sm text-[var(--text-secondary)] mb-1">
API Key <span class="text-[var(--text-tertiary)]">(optional override)</span>
{#if config.streamer_mode}
<span class="text-yellow-500 text-xs ml-2">🔒 Hidden (Streamer Mode)</span>
{/if}
</label>
<div class="relative">
<input
id="api-key"
type={showApiKey ? "text" : "password"}
bind:value={config.api_key}
placeholder="Falls back to ~/.claude settings"
class="w-full px-3 py-2 pr-10 bg-[var(--bg-primary)] border border-[var(--border-color)] rounded-lg text-[var(--text-primary)] focus:outline-none focus:border-[var(--accent-primary)]"
/>
<button
type="button"
onclick={() => (showApiKey = !showApiKey)}
class="absolute right-2 top-1/2 -translate-y-1/2 text-[var(--text-tertiary)] hover:text-[var(--text-primary)]"
aria-label={showApiKey ? "Hide API key" : "Show API key"}
>
{#if showApiKey}
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M13.875 18.825A10.05 10.05 0 0112 19c-4.478 0-8.268-2.943-9.543-7a9.97 9.97 0 011.563-3.029m5.858.908a3 3 0 114.243 4.243M9.878 9.878l4.242 4.242M9.88 9.88l-3.29-3.29m7.532 7.532l3.29 3.29M3 3l3.59 3.59m0 0A9.953 9.953 0 0112 5c4.478 0 8.268 2.943 9.543 7a10.025 10.025 0 01-4.132 5.411m0 0L21 21"
/>
</svg>
{:else}
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"
/>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"
/>
</svg>
{/if}
</button>
{#if config.streamer_mode}
<input
id="api-key"
type="password"
value="••••••••••••••••••••••••"
disabled
class="w-full px-3 py-2 pr-10 bg-[var(--bg-primary)] border border-[var(--border-color)] rounded-lg text-[var(--text-tertiary)] focus:outline-none cursor-not-allowed"
/>
{:else}
<input
id="api-key"
type={showApiKey ? "text" : "password"}
bind:value={config.api_key}
placeholder="Falls back to ~/.claude settings"
class="w-full px-3 py-2 pr-10 bg-[var(--bg-primary)] border border-[var(--border-color)] rounded-lg text-[var(--text-primary)] focus:outline-none focus:border-[var(--accent-primary)]"
/>
<button
type="button"
onclick={() => (showApiKey = !showApiKey)}
class="absolute right-2 top-1/2 -translate-y-1/2 text-[var(--text-tertiary)] hover:text-[var(--text-primary)]"
aria-label={showApiKey ? "Hide API key" : "Show API key"}
>
{#if showApiKey}
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M13.875 18.825A10.05 10.05 0 0112 19c-4.478 0-8.268-2.943-9.543-7a9.97 9.97 0 011.563-3.029m5.858.908a3 3 0 114.243 4.243M9.878 9.878l4.242 4.242M9.88 9.88l-3.29-3.29m7.532 7.532l3.29 3.29M3 3l3.59 3.59m0 0A9.953 9.953 0 0112 5c4.478 0 8.268 2.943 9.543 7a10.025 10.025 0 01-4.132 5.411m0 0L21 21"
/>
</svg>
{:else}
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"
/>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"
/>
</svg>
{/if}
</button>
{/if}
</div>
</div>
@@ -519,6 +534,45 @@
</div>
</section>
<!-- Privacy / Streamer Mode Section -->
<section class="mb-6">
<h3 class="text-sm font-medium text-[var(--accent-primary)] uppercase tracking-wider mb-3">
Privacy / Streamer Mode
</h3>
<!-- Streamer Mode Toggle -->
<div class="mb-4">
<label class="flex items-center gap-3 cursor-pointer">
<input
type="checkbox"
bind:checked={config.streamer_mode}
class="w-4 h-4 text-[var(--accent-primary)] bg-[var(--bg-primary)] border-[var(--border-color)] rounded focus:ring-[var(--accent-primary)] focus:ring-2"
/>
<span class="text-sm text-[var(--text-primary)]">Enable streamer mode</span>
</label>
<p class="text-xs text-[var(--text-tertiary)] mt-1 ml-7">
Hide sensitive information like API keys when streaming (Ctrl+Shift+S to toggle)
</p>
</div>
<!-- Hide Paths Toggle -->
{#if config.streamer_mode}
<div class="mb-4">
<label class="flex items-center gap-3 cursor-pointer">
<input
type="checkbox"
bind:checked={config.streamer_hide_paths}
class="w-4 h-4 text-[var(--accent-primary)] bg-[var(--bg-primary)] border-[var(--border-color)] rounded focus:ring-[var(--accent-primary)] focus:ring-2"
/>
<span class="text-sm text-[var(--text-primary)]">Also hide file paths</span>
</label>
<p class="text-xs text-[var(--text-tertiary)] mt-1 ml-7">
Mask directory paths (e.g., /home/user → /home/****)
</p>
</div>
{/if}
</section>
<!-- Notifications Section -->
<section class="mb-6">
<h3 class="text-sm font-medium text-[var(--accent-primary)] uppercase tracking-wider mb-3">