Playground
The Playground imports @agntn/ciphers into the page and runs it in your browser. Every encode, decode, brute force, histogram, key length and info card is the library's own answer, the same code the CLI and the agent tools run. Nothing is sent anywhere. The docs worker serves the page and that's the end of its involvement.
| Operation | Runs | Tool equivalent |
|---|---|---|
| Encode | create(cipher).encode(text, options) | ciphers_encode |
| Decode | create(cipher).decode(text, options) | ciphers_decode |
| Brute force | create("caesar").decode(text, { shift }) for 1 to 25, best analyzeFrequency(…, language).fit first | ciphers_caesar_brute |
| Frequency | analyzeFrequency(text, language) | ciphers_frequency |
| Period | estimatePeriod(text, language, maxPeriod), ten likeliest lengths, keys on the top three | ciphers_period_estimate |
| Info | create(cipher).info() | ciphers_info |
The option fields come from create(cipher).info().options. A cipher that gains an option shows it without anyone touching the page. preserveCase and stripNonAlpha are the two flags every classical cipher shares; a block cipher encrypts bytes, so the form hides them there.
Every state is a link
The form writes itself into the address: /playground?op=encode&cipher=vigenere&text=ATTACK+AT+DAWN&key=LEMON opens the page on that answer. Paste it to someone and they see what you saw. The permalink button copies the current one.
The two snippets
Under the fields the page prints the same call as a CLI line and as a tool invocation in JSON, both with copy buttons. They are built from the form, so what you copy is what you ran. Not an example that looks like it. The answer below ends in a Full tool response row that opens the exact text the tool hands a model, from the same executor the MCP server runs.
The landing
The panels on the home page walk through the seventy-two ciphers with one sample sentence each, computed the same way, in the browser, on one clock. Hover a panel to pause it. The arrows under the code step it by hand.