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
|
|
@ -5,6 +5,9 @@
|
|||
import { FitAddon } from '@xterm/addon-fit';
|
||||
import { ImageAddon } from '@xterm/addon-image';
|
||||
import { electrobun } from './main.ts';
|
||||
import { fontStore } from './font-store.svelte.ts';
|
||||
import { themeStore } from './theme-store.svelte.ts';
|
||||
import { getXtermTheme } from './themes.ts';
|
||||
|
||||
interface Props {
|
||||
sessionId: string;
|
||||
|
|
@ -14,34 +17,11 @@
|
|||
|
||||
let { sessionId, cwd }: Props = $props();
|
||||
|
||||
// Catppuccin Mocha terminal theme
|
||||
const THEME = {
|
||||
background: '#1e1e2e',
|
||||
foreground: '#cdd6f4',
|
||||
cursor: '#f5e0dc',
|
||||
cursorAccent: '#1e1e2e',
|
||||
selectionBackground: '#585b7066',
|
||||
black: '#45475a',
|
||||
red: '#f38ba8',
|
||||
green: '#a6e3a1',
|
||||
yellow: '#f9e2af',
|
||||
blue: '#89b4fa',
|
||||
magenta: '#f5c2e7',
|
||||
cyan: '#94e2d5',
|
||||
white: '#bac2de',
|
||||
brightBlack: '#585b70',
|
||||
brightRed: '#f38ba8',
|
||||
brightGreen: '#a6e3a1',
|
||||
brightYellow: '#f9e2af',
|
||||
brightBlue: '#89b4fa',
|
||||
brightMagenta: '#f5c2e7',
|
||||
brightCyan: '#94e2d5',
|
||||
brightWhite: '#a6adc8',
|
||||
};
|
||||
|
||||
let termEl: HTMLDivElement;
|
||||
let term: Terminal;
|
||||
let fitAddon: FitAddon;
|
||||
let unsubFont: (() => void) | null = null;
|
||||
let ro: ResizeObserver | null = null;
|
||||
|
||||
/** Decode a base64 string from the daemon into a Uint8Array. */
|
||||
function decodeBase64(b64: string): Uint8Array {
|
||||
|
|
@ -51,11 +31,15 @@
|
|||
return bytes;
|
||||
}
|
||||
|
||||
onMount(async () => {
|
||||
onMount(() => {
|
||||
const currentTheme = themeStore.currentTheme;
|
||||
const termFamily = fontStore.termFontFamily || 'JetBrains Mono, Fira Code, monospace';
|
||||
const termSize = fontStore.termFontSize || 13;
|
||||
|
||||
term = new Terminal({
|
||||
theme: THEME,
|
||||
fontFamily: "'JetBrains Mono', 'Fira Code', monospace",
|
||||
fontSize: 13,
|
||||
theme: getXtermTheme(currentTheme),
|
||||
fontFamily: termFamily,
|
||||
fontSize: termSize,
|
||||
cursorBlink: true,
|
||||
allowProposedApi: true,
|
||||
scrollback: 5000,
|
||||
|
|
@ -65,7 +49,6 @@
|
|||
term.loadAddon(fitAddon);
|
||||
term.loadAddon(new CanvasAddon());
|
||||
|
||||
// Sixel / iTerm2 / Kitty inline image support
|
||||
term.loadAddon(new ImageAddon({
|
||||
enableSizeReports: true,
|
||||
sixelSupport: true,
|
||||
|
|
@ -77,25 +60,34 @@
|
|||
term.open(termEl);
|
||||
fitAddon.fit();
|
||||
|
||||
// ── Connect to PTY daemon via Bun RPC ──────────────────────────────────
|
||||
// ── Subscribe to terminal font changes ─────────────────────────────────
|
||||
|
||||
const { cols, rows } = term;
|
||||
const result = await electrobun.rpc?.request["pty.create"]({ sessionId, cols, rows, cwd });
|
||||
if (!result?.ok) {
|
||||
term.writeln(`\x1b[31m[agor] Failed to connect to PTY daemon: ${result?.error ?? 'unknown error'}\x1b[0m`);
|
||||
term.writeln('\x1b[33m[agor] Is agor-ptyd running? Start it with: agor-ptyd\x1b[0m');
|
||||
}
|
||||
unsubFont = fontStore.onTermFontChange((family: string, size: number) => {
|
||||
term.options.fontFamily = family || 'JetBrains Mono, Fira Code, monospace';
|
||||
term.options.fontSize = size;
|
||||
fitAddon.fit();
|
||||
electrobun.rpc?.request['pty.resize']({ sessionId, cols: term.cols, rows: term.rows }).catch(() => {});
|
||||
});
|
||||
|
||||
// ── Receive output from daemon (via Bun) ───────────────────────────────
|
||||
// ── Connect to PTY daemon (fire-and-forget from onMount) ───────────────
|
||||
|
||||
// "pty.output" messages are pushed by Bun whenever the PTY produces data.
|
||||
electrobun.rpc?.addMessageListener("pty.output", ({ sessionId: sid, data }) => {
|
||||
void (async () => {
|
||||
const { cols, rows } = term;
|
||||
const result = await electrobun.rpc?.request['pty.create']({ sessionId, cols, rows, cwd });
|
||||
if (!result?.ok) {
|
||||
term.writeln(`\x1b[31m[agor] Failed to connect to PTY daemon: ${result?.error ?? 'unknown error'}\x1b[0m`);
|
||||
term.writeln('\x1b[33m[agor] Is agor-ptyd running? Start it with: agor-ptyd\x1b[0m');
|
||||
}
|
||||
})();
|
||||
|
||||
// ── Receive output from daemon ─────────────────────────────────────────
|
||||
|
||||
electrobun.rpc?.addMessageListener('pty.output', ({ sessionId: sid, data }: { sessionId: string; data: string }) => {
|
||||
if (sid !== sessionId) return;
|
||||
term.write(decodeBase64(data));
|
||||
});
|
||||
|
||||
// "pty.closed" fires when the shell exits.
|
||||
electrobun.rpc?.addMessageListener("pty.closed", ({ sessionId: sid, exitCode }) => {
|
||||
electrobun.rpc?.addMessageListener('pty.closed', ({ sessionId: sid, exitCode }: { sessionId: string; exitCode: number | null }) => {
|
||||
if (sid !== sessionId) return;
|
||||
term.writeln(`\r\n\x1b[90m[Process exited${exitCode !== null ? ` with code ${exitCode}` : ''}]\x1b[0m`);
|
||||
});
|
||||
|
|
@ -103,7 +95,7 @@
|
|||
// ── Send user input to daemon ──────────────────────────────────────────
|
||||
|
||||
term.onData((data: string) => {
|
||||
electrobun.rpc?.request["pty.write"]({ sessionId, data }).catch((err) => {
|
||||
electrobun.rpc?.request['pty.write']({ sessionId, data }).catch((err: unknown) => {
|
||||
console.error('[pty.write] error:', err);
|
||||
});
|
||||
});
|
||||
|
|
@ -111,22 +103,17 @@
|
|||
// ── Sync resize events to daemon ───────────────────────────────────────
|
||||
|
||||
term.onResize(({ cols: c, rows: r }) => {
|
||||
electrobun.rpc?.request["pty.resize"]({ sessionId, cols: c, rows: r }).catch(() => {});
|
||||
electrobun.rpc?.request['pty.resize']({ sessionId, cols: c, rows: r }).catch(() => {});
|
||||
});
|
||||
|
||||
// Refit on container resize and notify the daemon.
|
||||
const ro = new ResizeObserver(() => {
|
||||
fitAddon.fit();
|
||||
});
|
||||
ro = new ResizeObserver(() => { fitAddon.fit(); });
|
||||
ro.observe(termEl);
|
||||
|
||||
return () => ro.disconnect();
|
||||
});
|
||||
|
||||
onDestroy(() => {
|
||||
// Unsubscribe from daemon output (session stays alive so it can be
|
||||
// reconnected if the tab is re-opened, matching the "persists" requirement).
|
||||
electrobun.rpc?.request["pty.unsubscribe"]({ sessionId }).catch(() => {});
|
||||
unsubFont?.();
|
||||
ro?.disconnect();
|
||||
electrobun.rpc?.request['pty.unsubscribe']({ sessionId }).catch(() => {});
|
||||
term?.dispose();
|
||||
});
|
||||
</script>
|
||||
|
|
@ -140,7 +127,6 @@
|
|||
min-height: 10rem;
|
||||
}
|
||||
|
||||
/* xterm.js base styles */
|
||||
:global(.xterm) {
|
||||
padding: 0.5rem;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue