Skip to content

Commit 82cbb55

Browse files
committed
Add standalone browser token counter tool
1 parent 2cf0411 commit 82cbb55

4 files changed

Lines changed: 3423 additions & 0 deletions

File tree

index.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,7 @@ <h1>Support these tools</h1>
142142
<a href="https://github.com/dacode-dev/claude-code-power-pack">Claude Code Power Pack — GitHub</a>
143143
<a href="https://github.com/dacode-dev/vscode-ctxpack">ctxpack (VS Code extension) — GitHub</a>
144144
<a href="https://dacode-dev.github.io/repo-context-budgets.html">Why full-repo dumps waste your context budget — article</a>
145+
<a href="https://dacode-dev.github.io/token-counter.html">Token Counter — paste text, see the count, no upload</a>
145146
</div>
146147
</div>
147148

repo-context-budgets.html

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,8 @@ <h2>What this looks like in practice</h2>
117117

118118
<p>It's free, MIT-licensed, works with any tool that takes pasted-in text — Claude, Claude Code, ChatGPT, Cursor, Copilot Chat, or a custom agent's system prompt. Source and docs: <a href="https://github.com/dacode-dev/llm-ctxpack">github.com/dacode-dev/llm-ctxpack</a>.</p>
119119

120+
<p>Just want to check how many tokens a chunk of text is, without a whole repo involved? There's a standalone <a href="https://dacode-dev.github.io/token-counter.html">browser-based token counter</a> — paste text, see the count and how much of each model's context window it uses, nothing uploaded.</p>
121+
120122
<div class="back"><a href="https://dacode-dev.github.io/">← dacode-dev.github.io</a></div>
121123
</div>
122124
</body>

token-counter.html

Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
<!doctype html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="utf-8">
5+
<meta name="viewport" content="width=device-width, initial-scale=1">
6+
<title>Token Counter — paste text, see the token count and what fits your model</title>
7+
<meta name="description" content="Free browser-based token counter. Paste text or code, see the exact token count and how much of common LLM context windows it uses. No upload, runs entirely in your browser.">
8+
<style>
9+
:root {
10+
--bg: #0b0d10;
11+
--card: #14171c;
12+
--text: #e8eaed;
13+
--muted: #9aa0a6;
14+
--accent: #6ee7b7;
15+
--border: #262a30;
16+
--code-bg: #0e1013;
17+
--warn: #f59e0b;
18+
--danger: #f87171;
19+
}
20+
@media (prefers-color-scheme: light) {
21+
:root {
22+
--bg: #f7f7f8;
23+
--card: #ffffff;
24+
--text: #16181c;
25+
--muted: #5f6368;
26+
--accent: #059669;
27+
--border: #e2e4e8;
28+
--code-bg: #f1f2f4;
29+
--warn: #b45309;
30+
--danger: #b91c1c;
31+
}
32+
}
33+
* { box-sizing: border-box; }
34+
body {
35+
margin: 0;
36+
background: var(--bg);
37+
color: var(--text);
38+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
39+
display: flex;
40+
justify-content: center;
41+
padding: 40px 20px;
42+
}
43+
.card {
44+
max-width: 760px;
45+
width: 100%;
46+
background: var(--card);
47+
border: 1px solid var(--border);
48+
border-radius: 16px;
49+
padding: 32px;
50+
}
51+
h1 { font-size: 1.4rem; margin: 0 0 6px; }
52+
p.sub { color: var(--muted); margin: 0 0 24px; font-size: 0.9rem; }
53+
textarea {
54+
width: 100%;
55+
min-height: 260px;
56+
background: var(--code-bg);
57+
color: var(--text);
58+
border: 1px solid var(--border);
59+
border-radius: 10px;
60+
padding: 14px;
61+
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
62+
font-size: 0.85rem;
63+
resize: vertical;
64+
}
65+
.stats {
66+
display: flex;
67+
gap: 24px;
68+
margin: 16px 0 24px;
69+
flex-wrap: wrap;
70+
}
71+
.stat { }
72+
.stat .n { font-size: 1.6rem; font-weight: 700; }
73+
.stat .l { font-size: 0.8rem; color: var(--muted); }
74+
table {
75+
width: 100%;
76+
border-collapse: collapse;
77+
font-size: 0.88rem;
78+
}
79+
th, td { text-align: left; padding: 8px 10px; border-bottom: 1px solid var(--border); }
80+
th { color: var(--muted); font-weight: 600; font-size: 0.8rem; }
81+
.bar-wrap { width: 140px; background: var(--code-bg); border-radius: 6px; overflow: hidden; height: 8px; }
82+
.bar { height: 100%; background: var(--accent); }
83+
.bar.warn { background: var(--warn); }
84+
.bar.danger { background: var(--danger); }
85+
.pct { font-variant-numeric: tabular-nums; color: var(--muted); font-size: 0.8rem; }
86+
.note {
87+
font-size: 0.85rem;
88+
color: var(--muted);
89+
line-height: 1.5;
90+
border-top: 1px solid var(--border);
91+
padding-top: 20px;
92+
margin-top: 24px;
93+
}
94+
a { color: var(--accent); }
95+
</style>
96+
</head>
97+
<body>
98+
<div class="card">
99+
<h1>Token Counter</h1>
100+
<p class="sub">Paste text or code below. Counted entirely in your browser — nothing is uploaded anywhere.</p>
101+
102+
<textarea id="input" placeholder="Paste text, code, a prompt, anything..."></textarea>
103+
104+
<div class="stats">
105+
<div class="stat"><div class="n" id="tokenCount">0</div><div class="l">tokens</div></div>
106+
<div class="stat"><div class="n" id="charCount">0</div><div class="l">characters</div></div>
107+
<div class="stat"><div class="n" id="ratio"></div><div class="l">chars / token</div></div>
108+
</div>
109+
110+
<table>
111+
<thead>
112+
<tr><th>Model</th><th>Context window</th><th>Usage</th><th></th></tr>
113+
</thead>
114+
<tbody id="modelRows"></tbody>
115+
</table>
116+
117+
<div class="note">
118+
Uses <a href="https://www.npmjs.com/package/gpt-tokenizer">gpt-tokenizer</a> (cl100k encoding — the same tokenizer OpenAI's models use, and a close estimate for Claude and most other current models too). Context-window percentages already reserve 15% headroom for system prompt, conversation, and response — the same margin <a href="https://www.npmjs.com/package/llm-ctxpack">llm-ctxpack</a> uses when budgeting a repo pack.
119+
<br><br>
120+
Need this for a whole repo instead of pasted text? <a href="https://www.npmjs.com/package/llm-ctxpack">llm-ctxpack</a> does git-diff-aware, budget-aware repo packing with secret redaction built in: <code>npx llm-ctxpack . --budget 50000</code>.
121+
</div>
122+
</div>
123+
124+
<script src="tokenizer.bundle.js"></script>
125+
<script>
126+
const MODELS = [
127+
{ name: 'Claude (Opus / Sonnet / Haiku)', window: 200000 },
128+
{ name: 'GPT-4o / GPT-4.1 / GPT-4 Turbo', window: 128000 },
129+
{ name: 'GPT-3.5', window: 16000 },
130+
{ name: 'Gemini 1.5/2.x', window: 1000000 },
131+
{ name: 'Llama 3.1/3.2/3.3', window: 128000 },
132+
];
133+
const HEADROOM = 0.15;
134+
135+
const input = document.getElementById('input');
136+
const tokenCountEl = document.getElementById('tokenCount');
137+
const charCountEl = document.getElementById('charCount');
138+
const ratioEl = document.getElementById('ratio');
139+
const modelRows = document.getElementById('modelRows');
140+
141+
function render() {
142+
const text = input.value;
143+
const chars = text.length;
144+
let tokens = 0;
145+
try {
146+
tokens = text.length ? window.__tokenCounters.cl100k(text) : 0;
147+
} catch (e) {
148+
tokens = 0;
149+
}
150+
tokenCountEl.textContent = tokens.toLocaleString();
151+
charCountEl.textContent = chars.toLocaleString();
152+
ratioEl.textContent = tokens > 0 ? (chars / tokens).toFixed(2) : '—';
153+
154+
modelRows.innerHTML = MODELS.map(m => {
155+
const usable = Math.floor(m.window * (1 - HEADROOM));
156+
const pct = usable > 0 ? Math.min(100, (tokens / usable) * 100) : 0;
157+
const barClass = pct >= 90 ? 'danger' : pct >= 60 ? 'warn' : '';
158+
return `<tr>
159+
<td>${m.name}</td>
160+
<td>${m.window.toLocaleString()}</td>
161+
<td><div class="bar-wrap"><div class="bar ${barClass}" style="width:${pct}%"></div></div></td>
162+
<td class="pct">${pct.toFixed(1)}%</td>
163+
</tr>`;
164+
}).join('');
165+
}
166+
167+
input.addEventListener('input', render);
168+
render();
169+
</script>
170+
</body>
171+
</html>

0 commit comments

Comments
 (0)