collet 0.1.0

Relentless agentic coding orchestrator with zero-drop agent loops
Documentation
<script lang="ts">
	import { appStore } from '$lib/store.svelte.js';
	import { layoutStore, type Mode } from '$lib/stores/layout.svelte.js';
	import { projectsStore } from '$lib/stores/projects.svelte.js';

	const MODES: Mode[] = ['code', 'ask', 'architect'];

	function cycleMode() {
		const idx = MODES.indexOf(layoutStore.mode);
		layoutStore.mode = MODES[(idx + 1) % MODES.length];
	}

	function formatElapsed(secs: number): string {
		const m = Math.floor(secs / 60);
		const s = Math.floor(secs % 60);
		return m > 0 ? `${m}m${s.toString().padStart(2, '0')}s` : `${s}s`;
	}

	function formatTokens(n: number): string {
		if (n >= 1000) return `${(n / 1000).toFixed(1)}k`;
		return String(n);
	}

	function contextPercent(): number {
		const s = appStore.status;
		const max = appStore.contextMax || 128000;
		if (!s || !s.context_tokens) return 0;
		return Math.round((s.context_tokens / max) * 100);
	}

	function ctxBar(): string {
		const pct = contextPercent();
		const filled = Math.round((pct / 100) * 10);
		return '█'.repeat(filled) + '░'.repeat(10 - filled);
	}

	function totalTokens(): number {
		const s = appStore.status;
		if (!s) return 0;
		return s.prompt_tokens + s.completion_tokens;
	}

	function activeProjectName(): string {
		const proj = projectsStore.projects.find(
			(p) => p.id === projectsStore.activeProjectId
		);
		if (!proj?.working_dir) return '';
		const parts = proj.working_dir.replace(/\/+$/, '').split('/');
		return parts[parts.length - 1] || proj.working_dir;
	}

	const modeColors: Record<Mode, string> = {
		code: 'var(--red)',
		ask: 'var(--cyan)',
		architect: 'var(--magenta)'
	};
</script>

<div class="status-bar">
	<!-- Mode badge (clickable to cycle) -->
	<button
		class="mode-badge"
		style:background="color-mix(in srgb, {modeColors[layoutStore.mode]} 20%, transparent)"
		style:color={modeColors[layoutStore.mode]}
		style:border-color="color-mix(in srgb, {modeColors[layoutStore.mode]} 40%, transparent)"
		onclick={cycleMode}
		title="Click to cycle mode"
		aria-label="Agent mode: {layoutStore.mode}"
	>
		{layoutStore.mode}
	</button>

	<span class="sep">·</span>

	{#if appStore.agentActive}
		<span class="spinner" aria-label="Agent running"></span>
		{#if appStore.phase}
			<span class="phase">{appStore.phase}</span>
			<span class="sep">·</span>
		{/if}
	{/if}

	<!-- Context bar -->
	<span class="ctx-label">ctx</span>
	<span class="ctx-bar">{ctxBar()}</span>
	<span class="ctx-pct">{contextPercent()}%</span>

	{#if appStore.status}
		<span class="sep">·</span>
		<span class="stat">#{appStore.status.iteration}</span>
		<span class="stat">{formatElapsed(appStore.status.elapsed_secs)}</span>
		<span class="stat">T:{formatTokens(totalTokens())}</span>
	{/if}

	<span class="spacer"></span>

	<!-- Project name -->
	{#if activeProjectName()}
		<span class="project-name">{activeProjectName()}</span>
		<span class="sep">·</span>
	{/if}

	<!-- Model -->
	{#if appStore.model}
		<span class="model-name">{appStore.model}</span>
		<span class="sep">·</span>
	{/if}

	<!-- Version -->
	{#if appStore.serverVersion}
		<span class="version">v{appStore.serverVersion}</span>
	{/if}

	<!-- Queue indicator -->
	{#if appStore.messageQueue.length > 0}
		<span class="sep">·</span>
		<span class="queue-count" title="{appStore.messageQueue.length} message(s) queued">
			⟳{appStore.messageQueue.length}
		</span>
	{/if}
</div>

<style>
	.status-bar {
		display: flex;
		align-items: center;
		gap: 0.375rem;
		width: 100%;
		padding: 0.1875rem 0.625rem;
		font-family: var(--font-mono);
		font-size: 0.75rem;
		color: var(--fg-dim);
		background: var(--bg-surface);
		border-top: 1px solid var(--border);
		user-select: none;
		flex-shrink: 0;
		box-sizing: border-box;
		overflow: hidden;
	}

	.mode-badge {
		all: unset;
		cursor: pointer;
		padding: 0.0625rem 0.375rem;
		border-radius: 3px;
		border: 1px solid;
		font-size: 0.6875rem;
		font-weight: 700;
		text-transform: uppercase;
		letter-spacing: 0.04em;
		flex-shrink: 0;
	}

	.mode-badge:hover {
		filter: brightness(1.15);
	}

	.sep {
		color: var(--border);
		flex-shrink: 0;
	}

	.spinner {
		width: 0.5rem;
		height: 0.5rem;
		border: 1.5px solid var(--accent);
		border-top-color: transparent;
		border-radius: 50%;
		animation: spin 0.7s linear infinite;
		flex-shrink: 0;
	}

	.phase {
		color: var(--yellow);
		overflow: hidden;
		text-overflow: ellipsis;
		white-space: nowrap;
		max-width: 10rem;
	}

	.ctx-label {
		color: var(--fg-dim);
	}

	.ctx-bar {
		color: var(--accent);
		letter-spacing: -0.08em;
		font-size: 0.625rem;
	}

	.ctx-pct {
		min-width: 2.5ch;
	}

	.stat {
		flex-shrink: 0;
	}

	.spacer {
		flex: 1;
	}

	.project-name {
		color: var(--fg);
		font-weight: 500;
		overflow: hidden;
		text-overflow: ellipsis;
		white-space: nowrap;
		max-width: 10rem;
	}

	.model-name {
		color: var(--fg-dim);
		overflow: hidden;
		text-overflow: ellipsis;
		white-space: nowrap;
		max-width: 8rem;
	}

	.version {
		color: var(--fg-dim);
		flex-shrink: 0;
	}

	.queue-count {
		color: var(--yellow);
		flex-shrink: 0;
	}

	@keyframes spin {
		to { transform: rotate(360deg); }
	}
</style>