feat(electrobun): wire EVERYTHING — all settings persist, theme editor, marketplace
All settings wired to SQLite persistence: - AgentSettings: shell, CWD, permissions, providers (JSON blob) - SecuritySettings: branch policies (JSON array) - ProjectSettings: per-project via setProject RPC - OrchestrationSettings: wake, anchors, notifications - AdvancedSettings: logging, OTLP, plugins, import/export JSON Theme Editor: - 26 color pickers (14 Accents + 12 Neutrals) - Live CSS var preview as you pick colors - Save custom theme to SQLite, cancel reverts - Import/export theme as JSON - Custom themes in dropdown with delete button Extensions Marketplace: - 8-plugin demo catalog (Browse/Installed tabs) - Search/filter by name or tag - Install/uninstall with SQLite persistence - Plugin cards with emoji icons, tags, version Terminal font hot-swap: - fontStore.onTermFontChange() → xterm.js options update + fitAddon.fit() - Resize notification to PTY daemon after font change All 7 settings categories functional. Every control persists and takes effect.
This commit is contained in:
parent
6002a379e4
commit
5032021915
20 changed files with 1005 additions and 271 deletions
|
|
@ -1,4 +1,6 @@
|
|||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
import { appRpc } from '../main.ts';
|
||||
import { PROVIDER_CAPABILITIES, type ProviderId } from '../provider-capabilities';
|
||||
|
||||
type PermMode = 'bypassPermissions' | 'default' | 'plan';
|
||||
|
|
@ -14,10 +16,7 @@
|
|||
let permissionMode = $state<PermMode>('bypassPermissions');
|
||||
let systemPrompt = $state('');
|
||||
|
||||
interface ProviderState {
|
||||
enabled: boolean;
|
||||
model: string;
|
||||
}
|
||||
interface ProviderState { enabled: boolean; model: string; }
|
||||
let providerState = $state<Record<string, ProviderState>>({
|
||||
claude: { enabled: true, model: 'claude-opus-4-5' },
|
||||
codex: { enabled: false, model: 'gpt-5.4' },
|
||||
|
|
@ -26,15 +25,48 @@
|
|||
|
||||
let expandedProvider = $state<string | null>(null);
|
||||
|
||||
// ── Persistence helpers ──────────────────────────────────────────────────
|
||||
|
||||
function persist(key: string, value: string) {
|
||||
appRpc?.request['settings.set']({ key, value }).catch(console.error);
|
||||
}
|
||||
|
||||
function persistProviders() {
|
||||
persist('provider_settings', JSON.stringify(providerState));
|
||||
}
|
||||
|
||||
// ── Actions ──────────────────────────────────────────────────────────────
|
||||
|
||||
function setShell(v: string) { defaultShell = v; persist('default_shell', v); }
|
||||
function setCwd(v: string) { defaultCwd = v; persist('default_cwd', v); }
|
||||
function setPermMode(v: PermMode) { permissionMode = v; persist('permission_mode', v); }
|
||||
function setPrompt(v: string) { systemPrompt = v; persist('system_prompt_template', v); }
|
||||
|
||||
function toggleProvider(id: string) {
|
||||
providerState[id] = { ...providerState[id], enabled: !providerState[id].enabled };
|
||||
providerState = { ...providerState };
|
||||
persistProviders();
|
||||
}
|
||||
|
||||
function setModel(id: string, model: string) {
|
||||
providerState[id] = { ...providerState[id], model };
|
||||
providerState = { ...providerState };
|
||||
persistProviders();
|
||||
}
|
||||
|
||||
// ── Restore on mount ─────────────────────────────────────────────────────
|
||||
|
||||
onMount(async () => {
|
||||
if (!appRpc) return;
|
||||
const { settings } = await appRpc.request['settings.getAll']({}).catch(() => ({ settings: {} }));
|
||||
if (settings['default_shell']) defaultShell = settings['default_shell'];
|
||||
if (settings['default_cwd']) defaultCwd = settings['default_cwd'];
|
||||
if (settings['permission_mode']) permissionMode = settings['permission_mode'] as PermMode;
|
||||
if (settings['system_prompt_template']) systemPrompt = settings['system_prompt_template'];
|
||||
if (settings['provider_settings']) {
|
||||
try { providerState = JSON.parse(settings['provider_settings']); } catch { /* ignore */ }
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="section">
|
||||
|
|
@ -42,23 +74,27 @@
|
|||
|
||||
<div class="field">
|
||||
<label class="lbl" for="ag-shell">Shell</label>
|
||||
<input id="ag-shell" class="text-in" bind:value={defaultShell} placeholder="/bin/bash" />
|
||||
<input id="ag-shell" class="text-in" value={defaultShell} placeholder="/bin/bash"
|
||||
onchange={e => setShell((e.target as HTMLInputElement).value)} />
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label class="lbl" for="ag-cwd">Working directory</label>
|
||||
<input id="ag-cwd" class="text-in" bind:value={defaultCwd} placeholder="~" />
|
||||
<input id="ag-cwd" class="text-in" value={defaultCwd} placeholder="~"
|
||||
onchange={e => setCwd((e.target as HTMLInputElement).value)} />
|
||||
</div>
|
||||
|
||||
<h3 class="sh" style="margin-top: 0.75rem;">Permission mode</h3>
|
||||
<div class="seg">
|
||||
<button class:active={permissionMode === 'bypassPermissions'} onclick={() => permissionMode = 'bypassPermissions'}>Bypass</button>
|
||||
<button class:active={permissionMode === 'default'} onclick={() => permissionMode = 'default'}>Default</button>
|
||||
<button class:active={permissionMode === 'plan'} onclick={() => permissionMode = 'plan'}>Plan</button>
|
||||
<button class:active={permissionMode === 'bypassPermissions'} onclick={() => setPermMode('bypassPermissions')}>Bypass</button>
|
||||
<button class:active={permissionMode === 'default'} onclick={() => setPermMode('default')}>Default</button>
|
||||
<button class:active={permissionMode === 'plan'} onclick={() => setPermMode('plan')}>Plan</button>
|
||||
</div>
|
||||
|
||||
<h3 class="sh" style="margin-top: 0.75rem;">System prompt template</h3>
|
||||
<textarea class="prompt" bind:value={systemPrompt} rows="3" placeholder="Optional prompt prepended to all agent sessions..."></textarea>
|
||||
<textarea class="prompt" value={systemPrompt} rows="3"
|
||||
placeholder="Optional prompt prepended to all agent sessions..."
|
||||
onchange={e => setPrompt((e.target as HTMLTextAreaElement).value)}></textarea>
|
||||
|
||||
<h3 class="sh" style="margin-top: 0.75rem;">Providers</h3>
|
||||
<div class="prov-list">
|
||||
|
|
@ -74,24 +110,15 @@
|
|||
<div class="prov-body">
|
||||
<label class="toggle-row">
|
||||
<span class="lbl">Enabled</span>
|
||||
<button
|
||||
class="toggle"
|
||||
class:on={state.enabled}
|
||||
role="switch"
|
||||
aria-checked={state.enabled}
|
||||
aria-label="Toggle {prov.label} provider"
|
||||
onclick={() => toggleProvider(prov.id)}
|
||||
><span class="thumb"></span></button>
|
||||
<button class="toggle" class:on={state.enabled} role="switch"
|
||||
aria-checked={state.enabled} aria-label="Toggle {prov.label} provider"
|
||||
onclick={() => toggleProvider(prov.id)}><span class="thumb"></span></button>
|
||||
</label>
|
||||
<div class="field">
|
||||
<label class="lbl" for="model-{prov.id}">Default model</label>
|
||||
<input
|
||||
id="model-{prov.id}"
|
||||
class="text-in"
|
||||
value={state.model}
|
||||
<input id="model-{prov.id}" class="text-in" value={state.model}
|
||||
placeholder={PROVIDER_CAPABILITIES[prov.id].defaultModel}
|
||||
onchange={e => setModel(prov.id, (e.target as HTMLInputElement).value)}
|
||||
/>
|
||||
onchange={e => setModel(prov.id, (e.target as HTMLInputElement).value)} />
|
||||
</div>
|
||||
<div class="caps">
|
||||
{#if PROVIDER_CAPABILITIES[prov.id].images}<span class="cap">Images</span>{/if}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue