~ knotless Search/
↑↓ move↵ open⇧↵ all results

johnandersen777.bsky.social/knotless

git that lives in your Atmosphere account

Forked from chadtmiller.com/knotless

0
0
140 lines · 4.9 KB Raw History
<script lang="ts">
	import { onMount } from 'svelte';
	/** A conversation with an agent connected to knotless-mcp, played as it would happen: what you ask typed out, each tool call running then done, the answer streamed a word at a time. */
	type Line = { you: string } | { says: string } | { tool: string; detail: string };
	const lines: Line[] = [
		{
			you: 'Put this project on knotless as you.bsky.social/tally, and open an issue for each TODO in the README.'
		},
		{ tool: 'create_repo', detail: 'you.bsky.social/tally' },
		{ tool: 'commit', detail: 'main · 14 files' },
		{ tool: 'open_issue', detail: 'Dates in ISO 8601 are read as text' },
		{ tool: 'open_issue', detail: 'Totals round each line, not the sum' },
		{ says: 'tally is up, with two issues from the README’s TODOs.' },
		{ you: 'Fix the dates one and open a pull for it.' },
		{ tool: 'create_branch', detail: 'fix/iso-dates' },
		{ tool: 'commit', detail: 'fix/iso-dates · src/parse.rs, tests/dates.rs' },
		{ tool: 'open_pull', detail: 'Read ISO 8601 dates, fixes the dates issue' },
		{ tool: 'checks', detail: 'check passed · test passed' },
		{ says: 'The pull is open and its checks pass. It closes the issue when it merges.' },
		{ you: 'bob.bsky.social reviewed it. Anything to change?' },
		{ tool: 'show_item', detail: '1 comment from bob.bsky.social' },
		{ tool: 'commit', detail: 'fix/iso-dates · src/parse.rs' },
		{ tool: 'comment', detail: 'Kept the UTC offset, as you asked.' },
		{ tool: 'merge', detail: 'queued' },
		{
			says: 'Bob asked to keep the UTC offset, so I did, told him, and queued the pull: it lands once its batch passes.'
		}
	];
	// How long each beat takes, in ms: a key typed, a pause before sending,
	// the agent thinking, a tool call running, a word of its answer, and a
	// pause to read it before the next request.
	const KEY = 38;
	const SEND = 600;
	const THINK = 1100;
	const CALL = 900;
	const WORD = 90;
	const READ = 2200;

	// Lines before `at` are done, after it not yet shown; `part` is how far
	// into it: characters typed, words said, or 1 once a tool call is done.
	// Drawn finished until it plays, as it is without script or motion.
	let at = $state(lines.length);
	let part = $state(0);
	let thinking = $state(false);
	let run = 0;
	let box = $state<HTMLElement>();

	const words = (s: string) => s.split(' ');
	const wait = (ms: number, mine: number) =>
		new Promise<boolean>((done) => setTimeout(() => done(mine === run), ms));

	async function play() {
		const mine = ++run;
		for (let i = 0; i < lines.length; i++) {
			const line = lines[i];
			at = i;
			part = 0;
			if ('you' in line) {
				for (let c = 1; c <= line.you.length; c++) {
					part = c;
					if (!(await wait(KEY, mine))) return;
				}
				if (!(await wait(SEND, mine))) return;
			} else {
				if (!('tool' in lines[i - 1])) {
					thinking = true;
					if (!(await wait(THINK, mine))) return;
					thinking = false;
				}
				if ('tool' in line) {
					if (!(await wait(CALL, mine))) return;
					part = 1;
				} else {
					for (let w = 1; w <= words(line.says).length; w++) {
						part = w;
						if (!(await wait(WORD, mine))) return;
					}
					if (i < lines.length - 1 && !(await wait(READ, mine))) return;
				}
			}
		}
		at = lines.length;
	}

	onMount(() => {
		// A browser driven by a test, as the shots are, sees it finished.
		if (navigator.webdriver || matchMedia('(prefers-reduced-motion: reduce)').matches) return;
		at = -1;
		const seen = new IntersectionObserver(
			([e]) => {
				if (!e.isIntersecting) return;
				seen.disconnect();
				play();
			},
			{ threshold: 0.3 }
		);
		if (box) seen.observe(box);
		return () => {
			seen.disconnect();
			run++;
		};
	});
</script>

<figure class="agent-chat" bind:this={box}>
	<ol aria-live="off">
		{#each lines as line, i (i)}
			{@const now = i === at && !thinking}
			{@const hidden = i > at || (i === at && thinking)}
			{#if 'you' in line}
				<li class="you" class:hidden>
					<span class="who">You: </span>{#if now}<span>{line.you.slice(0, part)}</span><span
							class="rest">{line.you.slice(part)}</span
						>{:else}{line.you}{/if}
				</li>
			{:else if 'says' in line}
				<li class="says" class:hidden>
					{#if i === at && thinking}<span class="dots" aria-label="Thinking"
							><i></i><i></i><i></i></span
						>{/if}<span class="who">Claude: </span>{#if now}<span
							>{words(line.says).slice(0, part).join(' ')}</span
						><span class="rest">
							{words(line.says).slice(part).join(' ')}</span
						>{:else}{line.says}{/if}
				</li>
			{:else}
				<li class="tool" class:hidden class:running={now && part === 0}>
					{#if i === at && thinking}<span class="dots" aria-label="Thinking"
							><i></i><i></i><i></i></span
						>{/if}<b class="mark"></b><code>{line.tool}</code><span>{line.detail}</span>
				</li>
			{/if}
		{/each}
	</ol>
	<figcaption>
		Claude Code with knotless-mcp <button class="btn" type="button" onclick={play}
			>Play again</button
		>
	</figcaption>
</figure>