every-other-token 4.3.0

A real-time LLM stream interceptor for token-level interaction research
Documentation
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>every-other-token</title>
<meta name="description" content="A Rust CLI and web UI that sits on an LLM's token stream as it arrives, rewrites every other token, and shows per-token confidence and perplexity from the logprobs.">
<meta property="og:title" content="every-other-token">
<meta property="og:description" content="Intercept an LLM's token stream live: rewrite every other token, see how sure the model was about each one.">
<meta property="og:image" content="https://mattbusel.github.io/Every-Other-Token/assets/social-preview.png">
<meta name="twitter:card" content="summary_large_image">
<meta name="theme-color" content="#0c0d0b">
<link rel="icon" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'%3E%3Crect width='32' height='32' rx='7' fill='%230c0d0b'/%3E%3Crect x='6' y='20' width='8' height='3' rx='1.5' fill='%23e6e1d3'/%3E%3Crect x='18' y='20' width='8' height='3' rx='1.5' fill='%23ff6a2b'/%3E%3Crect x='6' y='10' width='8' height='6' rx='1' fill='%23e6e1d3'/%3E%3Crect x='18' y='10' width='8' height='6' rx='1' fill='%23ff6a2b'/%3E%3C/svg%3E">
<style>
:root{
  --bg:#0c0d0b;--bg2:#090a08;--card:#131411;--line:#25271f;--line2:#36382f;
  --ink:#e6e1d3;--dim:#a19c8c;--faint:#6b675b;--xf:#ff6a2b;--gold:#e8c872;
  --hi:#7fd69f;--mid:#e8c872;--lo:#ff5c7a;
  --mono:ui-monospace,'Cascadia Code','JetBrains Mono','SF Mono',Menlo,Consolas,'Liberation Mono',monospace;
}
*{box-sizing:border-box;margin:0;padding:0}
html{-webkit-text-size-adjust:100%}
body{background:var(--bg);color:var(--ink);font-family:var(--mono);font-size:15px;line-height:1.6;-webkit-font-smoothing:antialiased}
a{color:var(--ink);text-decoration-color:var(--line2);text-underline-offset:4px}
a:hover{text-decoration-color:var(--xf)}
.wrap{max-width:1120px;margin:0 auto;padding:0 28px}
.x{color:var(--xf)}
nav{display:flex;align-items:center;justify-content:space-between;padding:22px 0;gap:16px}
.logo{font-weight:700;font-size:16px;text-decoration:none;letter-spacing:-.01em;white-space:nowrap}
nav .links{display:flex;gap:22px;font-size:13px;color:var(--dim)}
nav .links a{color:var(--dim);text-decoration:none}
nav .links a:hover{color:var(--ink)}
header.hero{padding:56px 0 28px}
h1{font-size:clamp(34px,6.2vw,68px);line-height:1.02;letter-spacing:-.035em;font-weight:700}
.lead{max-width:62ch;color:var(--dim);font-size:17px;margin-top:22px}
.lead b{color:var(--ink);font-weight:500}
.player{margin-top:40px;border:1px solid var(--line);border-radius:14px;background:var(--bg2);overflow:hidden}
.pbar{display:flex;flex-wrap:wrap;align-items:center;gap:8px;padding:12px 14px;border-bottom:1px solid var(--line);background:var(--card)}
.pbar .lbl{font-size:12px;color:var(--faint);margin-right:4px}
.tab{font:inherit;font-size:13px;color:var(--dim);background:transparent;border:1px solid var(--line2);border-radius:6px;padding:4px 11px;cursor:pointer}
.tab:hover{color:var(--ink);border-color:var(--faint)}
.tab[aria-pressed=true]{background:var(--ink);color:var(--bg);border-color:var(--ink)}
.replay{margin-left:auto;font:inherit;font-size:13px;font-weight:700;background:var(--xf);color:#170904;border:0;border-radius:6px;padding:5px 14px;cursor:pointer}
.replay:hover{background:#ff8450}
.stage{display:grid;grid-template-columns:1fr 1fr;min-height:250px}
.lane{padding:22px 24px 26px;font-size:clamp(16px,2vw,21px);line-height:2.25;white-space:pre-wrap;word-wrap:break-word}
.lane+.lane{border-left:1px solid var(--line)}
.lane h3{font-size:11px;font-weight:500;letter-spacing:.12em;text-transform:uppercase;color:var(--faint);line-height:1;margin-bottom:14px}
.tk{text-decoration-line:underline;text-decoration-thickness:2px;text-underline-offset:8px;animation:tin .18s ease-out both}
.tk.h{text-decoration-color:var(--hi)}.tk.m{text-decoration-color:var(--mid)}.tk.l{text-decoration-color:var(--lo)}
.tk.x{color:var(--xf);font-weight:700}
.tk.del{color:var(--faint);text-decoration-line:line-through;text-decoration-color:var(--faint);font-weight:400}
.tk[data-ws]::before{content:attr(data-ws);display:inline-block;white-space:pre}
@keyframes tin{from{opacity:0;transform:translateY(3px)}to{opacity:1;transform:none}}
.caret{display:inline-block;width:.55em;height:1.05em;background:var(--xf);vertical-align:-.15em;margin-left:2px;animation:blink 1s steps(1) infinite}
@keyframes blink{50%{opacity:0}}
.pfoot{display:flex;flex-wrap:wrap;gap:8px 26px;align-items:center;padding:12px 24px;border-top:1px solid var(--line);font-size:12.5px;color:var(--faint)}
.pfoot b{color:var(--ink);font-weight:500}
.sw{display:inline-block;width:16px;height:3px;border-radius:2px;vertical-align:middle;margin-right:6px}
.legend{margin-left:auto;display:flex;gap:16px;flex-wrap:wrap}
.note{font-size:12.5px;color:var(--faint);margin-top:12px;max-width:80ch}
code{white-space:nowrap;font-family:var(--mono);font-size:.92em;color:var(--ink);background:var(--card);border:1px solid var(--line);border-radius:4px;padding:1px 5px}
section{padding:72px 0 8px}
h2{font-size:clamp(22px,3vw,30px);letter-spacing:-.02em;line-height:1.15;font-weight:700}
h2 .n{color:var(--faint);font-weight:400;margin-right:10px}
.sub{color:var(--dim);margin-top:10px;max-width:64ch}
.cmds{display:grid;gap:14px;margin-top:26px}
.cmd{border:1px solid var(--line);border-radius:10px;background:var(--card);overflow:hidden}
.cmd .hd{display:flex;justify-content:space-between;align-items:center;padding:8px 14px;border-bottom:1px solid var(--line);font-size:12px;color:var(--faint)}
.copy{font:inherit;font-size:12px;color:var(--dim);background:transparent;border:1px solid var(--line2);border-radius:5px;padding:2px 10px;cursor:pointer}
.copy:hover{color:var(--ink);border-color:var(--faint)}
.copy.ok{color:var(--hi);border-color:var(--hi)}
pre{margin:0;padding:14px 16px;overflow-x:auto;font-family:var(--mono);font-size:14px;line-height:1.7;color:var(--ink)}
pre .c{color:var(--faint)}
pre .p{color:var(--xf);user-select:none}
.shot{margin-top:26px}
.shot img{display:block;width:100%;height:auto;border-radius:12px;border:1px solid var(--line)}
.shot figcaption{font-size:12.5px;color:var(--faint);margin-top:10px}
.grid{display:grid;grid-template-columns:repeat(3,1fr);gap:1px;background:var(--line);border:1px solid var(--line);border-radius:12px;overflow:hidden;margin-top:28px}
.cell{background:var(--bg);padding:20px 20px 22px}
.cell h3{font-size:14px;font-weight:700;margin-bottom:6px}
.cell p{font-size:13.5px;color:var(--dim)}
.cell .k{color:var(--xf)}
footer{margin-top:90px;border-top:1px solid var(--line);padding:26px 0 40px;font-size:13px;color:var(--faint);display:flex;flex-wrap:wrap;gap:12px 24px;justify-content:space-between}
footer a{color:var(--dim)}
:focus-visible{outline:2px solid var(--gold);outline-offset:2px}
@media (max-width:820px){
  .wrap{padding:0 16px}
  .stage{grid-template-columns:1fr}
  .lane+.lane{border-left:0;border-top:1px solid var(--line)}
  .grid{grid-template-columns:1fr}
  .shot .scroll{overflow-x:auto;-webkit-overflow-scrolling:touch}
  .shot .scroll img{width:680px;max-width:none}
  nav .links{gap:14px}
  .legend{margin-left:0}
  header.hero{padding-top:28px}
}
@media (max-width:420px){nav .links .opt{display:none}}
@media (prefers-reduced-motion:reduce){.tk{animation:none}.caret{animation:none}}
</style>
</head>
<body>
<div class="wrap">
<nav>
  <a class="logo" href="./">every-<span class="x">other</span>-token</a>
  <div class="links">
    <a href="#install">install</a>
    <a class="opt" href="https://docs.rs/every-other-token">docs.rs</a>
    <a class="opt" href="https://crates.io/crates/every-other-token">crates.io</a>
    <a href="https://github.com/Mattbusel/Every-Other-Token">github</a>
  </div>
</nav>

<header class="hero">
  <h1>Read the stream,<br>not the <span class="x">answer</span>.</h1>
  <p class="lead"><b>every-other-token</b> is a Rust CLI and web UI that sits on an OpenAI or Anthropic token stream as it arrives. It rewrites every other token (or any fraction), and it tells you how sure the model was about each one: confidence <code>exp(logprob)</code> and perplexity <code>exp(-logprob)</code>, per token, live.</p>

  <div class="player" id="player">
    <div class="pbar" role="group" aria-label="Transform">
      <span class="lbl">transform</span>
      <button class="tab" data-t="reverse" aria-pressed="true">reverse</button>
      <button class="tab" data-t="uppercase" aria-pressed="false">uppercase</button>
      <button class="tab" data-t="mock" aria-pressed="false">mock</button>
      <button class="tab" data-t="noise" aria-pressed="false">noise</button>
      <button class="tab" data-t="delete" aria-pressed="false">delete</button>
      <button class="replay" id="replay">replay</button>
    </div>
    <div class="stage">
      <div class="lane"><h3>what the model sent</h3><span id="orig"></span></div>
      <div class="lane"><h3>what you see</h3><span id="shown"></span></div>
    </div>
    <div class="pfoot">
      <span><b id="n">0</b> tokens</span>
      <span><b id="nx">0</b> rewritten</span>
      <span>mean confidence <b id="mc">-</b></span>
      <span class="legend"><span><i class="sw" style="background:var(--hi)"></i>sure &ge;70%</span><span><i class="sw" style="background:var(--mid)"></i>unsure</span><span><i class="sw" style="background:var(--lo)"></i>guessing &lt;40%</span></span>
    </div>
  </div>
  <p class="note">Recorded from the real <code>/stream</code> endpoint of <code>every-other-token --web --provider mock</code>. The mock provider replays a fixed reply with fixed logprobs through the same interception pipeline a real model goes through, so it runs with no API key. Point it at OpenAI or Anthropic and the tokens and numbers are the model's own.</p>
</header>

<section id="install">
  <h2><span class="n">01</span>Running in two minutes</h2>
  <p class="sub">Prebuilt binaries for Linux x86_64, macOS (Apple Silicon and Intel) and Windows x86_64 are on the <a href="https://github.com/Mattbusel/Every-Other-Token/releases/latest">releases page</a>. Or build it with Cargo (Rust 1.81+).</p>
  <div class="cmds">
    <div class="cmd"><div class="hd"><span>install</span><button class="copy" data-copy="cargo install every-other-token">copy</button></div>
<pre><span class="p">$ </span>cargo install every-other-token</pre></div>
    <div class="cmd"><div class="hd"><span>try it, no API key</span><button class="copy" data-copy="every-other-token &quot;What is consciousness?&quot; --provider mock --visual">copy</button></div>
<pre><span class="p">$ </span>every-other-token "What is consciousness?" --provider mock --visual</pre></div>
    <div class="cmd"><div class="hd"><span>a real model, in the browser</span><button class="copy" data-copy="export OPENAI_API_KEY=sk-...
every-other-token --web">copy</button></div>
<pre><span class="p">$ </span>export OPENAI_API_KEY=sk-...   <span class="c"># or ANTHROPIC_API_KEY with --provider anthropic</span>
<span class="p">$ </span>every-other-token --web        <span class="c"># http://localhost:8888</span></pre></div>
  </div>
</section>

<section>
  <h2><span class="n">02</span>In the terminal</h2>
  <p class="sub">The stream prints as it arrives. With <code>--visual</code>, rewritten tokens are highlighted; <code>--heatmap</code> colors each token by importance instead.</p>
  <figure class="shot"><div class="scroll"><img src="assets/terminal-visual.svg" width="957" height="438" alt="Terminal: every-other-token with the mock provider, every other word reversed and highlighted, 22 tokens streamed, 11 transformed" loading="lazy"></div><figcaption>Real output of the command above.</figcaption></figure>
</section>

<section>
  <h2><span class="n">03</span>Every token carries its numbers</h2>
  <p class="sub">Each intercepted token is an event with the original text, what was shown, its index, whether it was rewritten, confidence, perplexity and the top alternatives. Consume them from the library, as JSON lines with <code>--json-stream</code>, or export them to CSV, JSONL or a self-contained HTML heatmap.</p>
  <figure class="shot"><div class="scroll"><img src="assets/terminal-table.svg" width="1060" height="772" alt="Per-token table from cargo run --example mock_stream: index, original token, shown token, confidence, perplexity" loading="lazy"></div><figcaption><code>cargo run --example mock_stream</code>, from a clone of the repo.</figcaption></figure>
</section>

<section>
  <h2><span class="n">04</span>The web UI</h2>
  <p class="sub"><code>--web</code> serves a single page with no build step and no external scripts. Single, split, quad (four transforms at once), OpenAI vs Anthropic diff, A/B system prompts, a research dashboard, JSON and CSV export, and collaborative rooms where several people edit tokens mid-stream.</p>
  <figure class="shot"><div class="scroll"><img src="assets/web-ui.png" width="1280" height="600" alt="The web UI in split view: original stream on the left, transformed on the right, each token underlined by confidence, with perplexity and confidence sparklines below" loading="lazy"></div><figcaption>Split view, mock provider. The underline under each token is its confidence.</figcaption></figure>
</section>

<section>
  <h2><span class="n">05</span>What else is in the box</h2>
  <div class="grid">
    <div class="cell"><h3>9 transforms, chainable</h3><p><span class="k">reverse</span>, uppercase, mock, noise, chaos, scramble, delete, synonym, <span class="k">delay:N</span>, or a chain like <code>reverse,uppercase</code>.</p></div>
    <div class="cell"><h3>Rate, seed, gating</h3><p><code>--rate 0.3</code> spreads rewrites evenly at any fraction. <code>--seed</code> makes random transforms reproducible. <code>--min-confidence</code> only touches tokens the model was unsure of.</p></div>
    <div class="cell"><h3>Provider diff</h3><p><code>--diff-terminal</code> streams OpenAI and Anthropic side by side and compares their confidence structure.</p></div>
    <div class="cell"><h3>A/B system prompts</h3><p>Run two system prompts against the same user prompt, many times, and test the confidence shift with Welch's t-test (<code>--significance</code>).</p></div>
    <div class="cell"><h3>Research mode</h3><p><code>--research --runs 20</code> runs headless and writes aggregate perplexity, confidence and vocabulary stats to JSON.</p></div>
    <div class="cell"><h3>Record and replay</h3><p>Record any session to JSON and replay it later, for example to spot behavior changes after a model update.</p></div>
  </div>
</section>

<footer>
  <span>MIT licensed. Built in Rust by <a href="https://github.com/Mattbusel">Matthew Busel</a>.</span>
  <span><a href="https://github.com/Mattbusel/Every-Other-Token">source</a> &nbsp; <a href="https://github.com/Mattbusel/Every-Other-Token/releases/latest">releases</a> &nbsp; <a href="https://docs.rs/every-other-token">API docs</a> &nbsp; <a href="https://github.com/Mattbusel/Every-Other-Token/issues">issues</a></span>
</footer>
</div>

<script>
const REC = {"reverse":[["The","The",0,0.887,1.127],[" quick"," kciuq",1,0.638,1.568],[" brown"," brown",0,0.458,2.181],[" fox"," xof",1,0.795,1.259],[" jumps"," jumps",0,0.571,1.751],[" over"," revo",1,0.712,1.405],[" the"," the",0,0.896,1.116],[" lazy"," yzal",1,0.411,2.435],[" dog"," dog",0,0.827,1.209],[".",".",1,0.932,1.073],[" This"," This",0,0.538,1.859],[" is"," si",1,0.861,1.162],[" a"," a",0,0.923,1.083],[" mock"," kcom",1,0.733,1.363],[" response"," response",0,0.644,1.553],[" for"," rof",1,0.763,1.31],[" prompt"," prompt",0,0.589,1.699],[":",":",1,0.835,1.197],[" What is consciousness?"," What is consciousness?",0,0.492,2.034]],"uppercase":[["The","The",0,0.887,1.127],[" quick"," QUICK",1,0.638,1.568],[" brown"," brown",0,0.458,2.181],[" fox"," FOX",1,0.795,1.259],[" jumps"," jumps",0,0.571,1.751],[" over"," OVER",1,0.712,1.405],[" the"," the",0,0.896,1.116],[" lazy"," LAZY",1,0.411,2.435],[" dog"," dog",0,0.827,1.209],[".",".",1,0.932,1.073],[" This"," This",0,0.538,1.859],[" is"," IS",1,0.861,1.162],[" a"," a",0,0.923,1.083],[" mock"," MOCK",1,0.733,1.363],[" response"," response",0,0.644,1.553],[" for"," FOR",1,0.763,1.31],[" prompt"," prompt",0,0.589,1.699],[":",":",1,0.835,1.197],[" What is consciousness?"," What is consciousness?",0,0.492,2.034]],"mock":[["The","The",0,0.887,1.127],[" quick"," qUiCk",1,0.638,1.568],[" brown"," brown",0,0.458,2.181],[" fox"," fOx",1,0.795,1.259],[" jumps"," jumps",0,0.571,1.751],[" over"," oVeR",1,0.712,1.405],[" the"," the",0,0.896,1.116],[" lazy"," lAzY",1,0.411,2.435],[" dog"," dog",0,0.827,1.209],[".",".",1,0.932,1.073],[" This"," This",0,0.538,1.859],[" is"," iS",1,0.861,1.162],[" a"," a",0,0.923,1.083],[" mock"," mOcK",1,0.733,1.363],[" response"," response",0,0.644,1.553],[" for"," fOr",1,0.763,1.31],[" prompt"," prompt",0,0.589,1.699],[":",":",1,0.835,1.197],[" What is consciousness?"," What is consciousness?",0,0.492,2.034]],"noise":[["The","The",0,0.887,1.127],[" quick"," quick~",1,0.638,1.568],[" brown"," brown",0,0.458,2.181],[" fox"," fox+",1,0.795,1.259],[" jumps"," jumps",0,0.571,1.751],[" over"," over$",1,0.712,1.405],[" the"," the",0,0.896,1.116],[" lazy"," lazy%",1,0.411,2.435],[" dog"," dog",0,0.827,1.209],[".",".*",1,0.932,1.073],[" This"," This",0,0.538,1.859],[" is"," is+",1,0.861,1.162],[" a"," a",0,0.923,1.083],[" mock"," mock*",1,0.733,1.363],[" response"," response",0,0.644,1.553],[" for"," for+",1,0.763,1.31],[" prompt"," prompt",0,0.589,1.699],[":",":$",1,0.835,1.197],[" What is consciousness?"," What is consciousness?",0,0.492,2.034]],"delete":[["The","The",0,0.887,1.127],[" quick"," ",1,0.638,1.568],[" brown"," brown",0,0.458,2.181],[" fox"," ",1,0.795,1.259],[" jumps"," jumps",0,0.571,1.751],[" over"," ",1,0.712,1.405],[" the"," the",0,0.896,1.116],[" lazy"," ",1,0.411,2.435],[" dog"," dog",0,0.827,1.209],[".","",1,0.932,1.073],[" This"," This",0,0.538,1.859],[" is"," ",1,0.861,1.162],[" a"," a",0,0.923,1.083],[" mock"," ",1,0.733,1.363],[" response"," response",0,0.644,1.553],[" for"," ",1,0.763,1.31],[" prompt"," prompt",0,0.589,1.699],[":","",1,0.835,1.197],[" What is consciousness?"," What is consciousness?",0,0.492,2.034]]};
const $ = s => document.querySelector(s);
const band = c => c >= 0.7 ? 'h' : c >= 0.4 ? 'm' : 'l';
let timer = null, cur = 'reverse';
const reduce = matchMedia('(prefers-reduced-motion: reduce)').matches;
function span(text, cls, title) {
  const s = document.createElement('span');
  s.className = 'tk ' + cls;
  const m = /^(\s+)(\S[\s\S]*)$/.exec(text);
  if (m) { s.dataset.ws = m[1]; s.textContent = m[2]; } else s.textContent = text;
  if (title) s.title = title;
  return s;
}
function play(t) {
  cur = t; clearTimeout(timer);
  document.querySelectorAll('.tab').forEach(b => b.setAttribute('aria-pressed', b.dataset.t === t));
  const o = $('#orig'), v = $('#shown'); o.textContent = ''; v.textContent = '';
  const caret = document.createElement('span'); caret.className = 'caret'; caret.setAttribute('aria-hidden', 'true');
  v.appendChild(caret);
  const rows = REC[t]; let i = 0, sum = 0, nx = 0;
  const step = () => {
    const [orig, shown, x, conf, ppl] = rows[i];
    const title = 'confidence ' + Math.round(conf * 100) + '%, perplexity ' + ppl.toFixed(2);
    const so = span(orig, band(conf), title);
    if (so.dataset.ws) { o.appendChild(document.createTextNode(so.dataset.ws)); delete so.dataset.ws; }
    o.appendChild(so);
    const deleted = x && shown.trim() === '';
    const sv = span(deleted ? orig : shown, band(conf) + (x ? (deleted ? ' del' : ' x') : ''), title + (x ? (deleted ? ', deleted' : ', rewritten') : ''));
    if (sv.dataset.ws) { v.insertBefore(document.createTextNode(sv.dataset.ws), caret); delete sv.dataset.ws; }
    v.insertBefore(sv, caret);
    sum += conf; nx += x; i++;
    $('#n').textContent = i; $('#nx').textContent = nx; $('#mc').textContent = Math.round(sum / i * 100) + '%';
    if (i < rows.length) timer = setTimeout(step, reduce ? 0 : 70 + Math.round(ppl * 45));
    else caret.remove();
  };
  step();
}
document.querySelectorAll('.tab').forEach(b => b.addEventListener('click', () => play(b.dataset.t)));
$('#replay').addEventListener('click', () => play(cur));
document.querySelectorAll('.copy').forEach(b => b.addEventListener('click', async () => {
  try { await navigator.clipboard.writeText(b.dataset.copy); b.textContent = 'copied'; b.classList.add('ok'); }
  catch (_) { b.textContent = 'select + copy'; }
  setTimeout(() => { b.textContent = 'copy'; b.classList.remove('ok'); }, 1400);
}));
const io = new IntersectionObserver(es => { if (es[0].isIntersecting) { play('reverse'); io.disconnect(); } });
io.observe($('#player'));
</script>
</body>
</html>