Something went wrong. Try again.
small gleam coding and (not yet) persistent agent daemon with a detachable cli forked from okami.mom/albedo
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107<script lang="ts"> import { tick } from 'svelte'; import { copyText, stopShellCommand, type ShellCommand } from '../api'; import { ShellOutput } from '../shell-commands.svelte';
let { sessionId, commandId, oncommand }: { sessionId: string; commandId: string; oncommand?: (command: ShellCommand) => void } = $props(); // svelte-ignore state_referenced_locally const output = new ShellOutput(sessionId, commandId); let scroller = $state<HTMLElement>(); let pinned = true; let lastStart = 0; let lastFollowing = true; let stopping = $state(false); let copied = $state(false); let problem = $state(''); const command = $derived(output.command); const status = $derived(command?.status === 'completed' ? `exit ${command.exit_code ?? '?'}` : command?.status === 'cancelled' ? 'stopped' : command?.status ?? (output.problem ? 'unavailable' : 'loading…')); const failed = $derived(command?.status === 'failed' || (command?.status === 'completed' && command.exit_code !== 0)); const partial = $derived(output.start > 0 || output.end < (command?.output_bytes ?? 0));
$effect(() => { output.watch(); return () => output.dispose(); }); $effect(() => { if (command) oncommand?.(command); }); $effect(() => { output.text; const start = output.start; const following = output.following; tick().then(() => { if (scroller) { if (!following && (start !== lastStart || lastFollowing)) scroller.scrollTop = 0; else if (following && pinned) scroller.scrollTop = scroller.scrollHeight; } lastStart = start; lastFollowing = following; }); }); $effect(() => { if (!copied) return; const timer = setTimeout(() => (copied = false), 1500); return () => clearTimeout(timer); });
async function copy() { problem = ''; try { await copyText(output.text); copied = true; } catch (error) { problem = String((error as Error).message ?? error); } } async function stop() { stopping = true; problem = ''; try { await stopShellCommand(sessionId, commandId); output.refresh(); } catch (error) { problem = String((error as Error).message ?? error); } finally { stopping = false; } } function scrolled() { if (scroller) pinned = scroller.scrollHeight - scroller.scrollTop - scroller.clientHeight < 24; } function latest() { pinned = true; output.latest(); }</script>
<div class="command-view"> {#if command}<div class="intent"><span>$</span><code>{command.command}</code></div>{/if} {#if command?.truncated}<p class="notice">Capture hit the 16 MiB retention limit; later output wasn’t retained.</p>{/if} {#if command?.error}<p class="problem" role="alert">{command.error}</p>{/if} {#if output.problem || problem} <div class="failure" role="alert"><span>{output.problem || problem}</span><button onclick={() => output.refresh()}>Retry</button></div> {/if} <!-- svelte-ignore a11y_no_noninteractive_tabindex (the output region must be keyboard-scrollable) --> <pre role="region" aria-label="Shell command output" tabindex="0" class="nowheel" bind:this={scroller} onscroll={scrolled}>{#if output.start > 0}<span class="quiet">… {output.start.toLocaleString()} earlier bytes not shown</span>{/if}{output.text}{#if !output.text && !output.problem}<span class="quiet">{!command || output.loading ? 'Loading output…' : command.status === 'running' ? 'Waiting for output…' : 'No output.'}</span>{/if}</pre> <footer> <span class="status" class:failed role="status" title={command ? `${command.status}${command.exit_code !== null ? ` · exit ${command.exit_code}` : ''}` : undefined}>{status}</span> <span class="range" title="UTF-8 bytes currently shown">{output.start.toLocaleString()}–{output.end.toLocaleString()} of {(command?.output_bytes ?? 0).toLocaleString()} B</span> {#if output.text}<button onclick={copy} title={partial ? 'Copy loaded output' : 'Copy output'}>{copied ? 'Copied' : partial ? 'Copy loaded' : 'Copy'}</button>{/if} {#if command?.status === 'running'}<button class="stop" disabled={stopping} onclick={stop}>{stopping ? 'Stopping…' : 'Stop'}</button>{/if} </footer> <nav aria-label="Command output pages"> <button disabled={output.loading || !command?.output_bytes} onclick={() => output.first()}>Read from start</button> {#if !output.following} <button disabled={output.loading || output.start === 0} onclick={() => output.previous()}>Previous</button> <button disabled={output.loading || output.end >= (command?.output_bytes ?? 0)} onclick={() => output.next()}>Next</button> {/if} <button class="follow" aria-pressed={output.following} disabled={output.loading} onclick={latest}>{output.following ? 'Following latest' : 'Follow latest'}</button> </nav></div>
<style> .command-view { display: flex; flex: 1; flex-direction: column; min-height: 0; min-width: 0; color: var(--text); } .intent { display: flex; flex: none; gap: 9px; max-height: 6em; overflow: auto; padding: 12px 16px; font: calc(12px * var(--canvas-scale)) / 1.5 var(--mono); } .intent > span { color: var(--accent); } code { min-width: 0; white-space: pre-wrap; overflow-wrap: anywhere; font: inherit; } pre { flex: 1; min-height: 0; margin: 0; padding: 12px 16px; overflow: auto; overscroll-behavior: contain; scrollbar-width: thin; white-space: pre-wrap; overflow-wrap: anywhere; tab-size: 2; background: var(--well); font: calc(12px * var(--canvas-scale)) / 1.5 var(--mono); } .quiet, .range { color: var(--muted); } footer, nav { display: flex; flex: none; flex-wrap: wrap; align-items: center; gap: 8px 12px; padding: 7px 12px; font: calc(11px * var(--canvas-scale)) / 1.5 var(--sans); } footer { background: var(--title-bar); } .range { margin-right: auto; font-variant-numeric: tabular-nums; } .follow { margin-left: auto; } .follow[aria-pressed='true'] { color: var(--accent); } button { border: 0; padding: 3px 0; background: none; color: var(--muted); cursor: pointer; font: inherit; } button:hover { color: var(--text); } button:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; } button:disabled { opacity: .5; cursor: default; } .failed, .stop, .problem, .failure { color: var(--danger); } .notice, .problem, .failure { flex: none; margin: 0; padding: 8px 16px; overflow-wrap: anywhere; font: calc(11px * var(--canvas-scale)) / 1.5 var(--sans); } .notice { color: var(--muted); } .failure { display: flex; gap: 12px; align-items: baseline; }</style>