ID@agntn/encodingsv0.0.0

Decode it. Don't eyeball it.

Ask a model what a base58 string says and it reads the vibe. This reads the bytes. Base64, base58 with its checksum, bech32 down to the witness program, and the rest. Written from the RFCs and BIPs. One call in TypeScript, the terminal and your agent. No network, nothing to configure.

Encodings
20
in 11 families
With checksum
3
4 agent tools on top
Network calls
0
every value computed in place
Install$ pnpm add @agntn/encodings
Callencode("base64", "hello world!")base64 · 12 / 20
Encoding / base64

Base64

Three bytes as four characters. MIME, data URLs, PEM.

Alphabet

In12 bytes
OutaGVsbG8gd29ybGQh16 chars
Size
12 bytes → 16 chars
Overhead
+33% · 13 of 64 used
Padding
= to a whole block
Standard
RFC 4648 §4
family 2 / 20

Same call, every encoding

Pick a name, hand it bytes, get text back. Then do it backwards. This file walks through 20 encodings and none of it is a recording. Your browser runs every line with the same TypeScript the package ships. Round trip true, every time? That's the whole point. Bech32 is the fun one, its prefix comes back in details instead of hiding in the bytes.

  • encode(name, input) and decode(name, text). That's the whole API you need on day one
  • Strings go in as UTF-8. A Uint8Array goes in as it is
  • Names forgive you: Base 64, base_64 and b64 all find base64
Read Encoding and decoding
Filebase64.tsbase64 · computed here

Round trip

import { decode, encode, identify } from "@agntn/encodings";// Base64, RFC 4648 §4const text = encode("base64", "hello world!");// "aGVsbG8gd29ybGQh"const { bytes, details } = decode("base64", text);bytes.length;  // 12, details {}encode("base64", bytes) === text;  // trueidentify(text)[0]?.encoding;// "base64"

A guess that shows its work

Someone pastes MZXW6=== and asks what it is. A model squints and says base64, probably. identify tries every encoding it knows and ranks the ones that actually decode. Each score comes with its reasons, so you can disagree with it. The confidence ranks, it doesn't promise. And when nothing fits? It says so, instead of inventing an answer.

  • A checksum that matches beats everything. Base58Check and bech32 prove themselves
  • Then framing: padding that fits, <~ ~>, a begin line, =XX escapes
  • Then text that reads like text, then the smaller alphabet
Read Identify
Callencodings_identify("aGVsbG8gd29ybGQh")ranked
Guess / 0.371

base64, it is

Decodes to readable text.

  1. base64"hello world!"0.371
  2. base64url"hello world!"0.371
  3. base32-crockfordhex 543795c1106893e5c2f10.043
7 of 20 read it

Twenty encodings, five ways to spell bytes

Bit groups, where every character carries the same few bits. Base58, which treats the whole input as one huge number. Fixed blocks like Ascii85 and basE91. Bech32, where a checksum catches your typos for you. And two mail formats older than most of the internet. Three of them check themselves on decode. Every one written from its RFC, BIP or reference code, with no codec library underneath.

Importimport { encodings, create } from "@agntn/encodings"

Callencodings()20 names · 5 ways to spell bytes

Bit groups

Big number

Fixed blocks

Checksummed words

Mail

Four tools, one executor

Ask a model to decode base58 and it decodes the vibe. Give it encodings_decode and it decodes the string. encodings mcp, the Pi and OMP extensions and @agntn/encodings/ai all call the same executors. This page runs them too, so the dialog shows exactly what a model reads for "hello world!".

  • encodings_encode, encodings_decode, encodings_identify, encodings_info
  • A misspelled argument is an error, not a silent default
  • Bytes come back as text when they read as text, as hex when they don't
Read MCP, Pi, OMP and AI SDK
Callencodings_decode("base64", "aGVsbG8gd29ybGQh")
Tool / Base64

Base64

The bytes come out of the executor, not out of the model's imagination. Readable text comes back as text, anything else as hex.

bytes
12 as utf8
value
hello world!
carried
nothing but data
MCP · Pi · OMPencodings mcp · stdio

Your own encoding is one object

Need octal because some puzzle printed od -b output? Write the object, register it, and the registry treats it like a built-in. Even identify picks it up. No plugin manifest, no base class to fight. Throw a DecodeError for text that's wrong, and the CLI and the MCP server report it like one of their own. The alphabet you declare even feeds the small alphabet score.

  • A name, info(), encode and decode. That's the Encoding interface
  • register(encoding) and encode, decode and the tools see it
  • identify tries it too, no extra wiring
Read Custom encodings
Fileoctal.tsfolded · copy is whole
import { DecodeError, encode, identify, register, type Encoding } from "@agntn/encodings";const octal: Encoding = {  name: "octal",  info: () => ({ /* label, family, standard, alphabet, options */ }),  encode: (input) => /* three octal digits per byte, spaced */,  decode(text) { /* split, check 000 to 377, parseInt */ },};register(octal);encode("octal", "hi");                // "150 151"identify("150 151")[0]?.encoding;    // "octal"
Startpnpm add @agntn/encodingsNode.js 26 or newer

Start with one command

One install gets you the library, the encodings CLI and the MCP server. Name an encoding, hand it text or bytes, done. Got a string and no idea what it is? That's what identify is for.

  • PinPre-1.0, so pin exact versions.
  • Not auditedFine for puzzles and tooling. Not for your savings.
  • OfflineNothing to fetch, no key to sign up for, nothing to configure.

First call

$ pnpm add @agntn/encodingsimport { base58check, decode, encode, identify } from "@agntn/encodings";encode("base64", "gm");  // "Z20="base58check.decode("1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa");  // 21 bytes, version 0x00decode("bech32", "bc1qw508…").details;  // { prefix: "bc", witnessVersion: 0 }identify("1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa")[0].encoding;  // "base58check"