zefer-cli is three channels over one core:
| Channel | Entry | Use it for |
|---|---|---|
| CLI | zefer … (bin) |
Humans & shell scripts |
| MCP server | zefer mcp |
AI agents (Claude, Cursor, …) — see MCP.md |
| Library | import "zefer-cli" |
Your own Node.js code, services, AWS Lambda |
All three produce byte-for-byte compatible .zefer files, fully decryptable in
the web app at zefer.carrillo.app and vice-versa.
The library entry (src/lib.ts → dist/lib.js / dist/lib.cjs) contains no
Ink, no React, no Commander and no process.argv parsing — importing it has
zero side effects, so it is safe in any Node 20+ runtime.
npm install zefer-cli// ESM / TypeScript
import { encodeZefer, decodeZefer } from "zefer-cli";
// CommonJS
const { encodeZefer, decodeZefer } = require("zefer-cli");Ships ESM (dist/lib.js) and CommonJS (dist/lib.cjs) builds with full
TypeScript declarations (dist/lib.d.ts / dist/lib.d.cts).
The package
bin(zefer/zefer-cli) is unchanged and independent — the library is the package's default import; the CLI is reached through the bin.
import { encodeZefer, decodeZefer } from "zefer-cli";
import { readFile, writeFile } from "node:fs/promises";
// ── Encrypt text ──────────────────────────────────────────────
const buf = await encodeZefer({
content: "api_key=abc123",
passphrase: "a-strong-passphrase",
fileName: null,
expiresAt: 0, // 0 = never; otherwise a Unix-ms timestamp
compression: "gzip", // "none" | "gzip" | "deflate" | "deflate-raw"
iterations: 600_000, // explicit — see "Iterations" below
});
await writeFile("secret.zefer", buf);
// ── Decrypt ───────────────────────────────────────────────────
const bytes = await readFile("secret.zefer");
const res = await decodeZefer(bytes.toString("utf-8"), "a-strong-passphrase", {
rawBytes: bytes, // required for ZEFB3/ZEFR3 binary files
});
if (res.ok) {
console.log(res.payload.content); // text mode → string
// res.payload.fileData // file mode → Buffer
// res.payload.meta // ZeferMeta (createdAt, expiresAt, …)
} else {
console.error(res.error); // "wrong_passphrase" | "expired" | …
}const data = await readFile("photo.jpg");
const buf = await encodeZefer({
fileData: data,
fileName: "photo.jpg",
fileType: "image/jpeg",
passphrase: "a-strong-passphrase",
expiresAt: 0,
iterations: 600_000,
});import { parseFile } from "zefer-cli";
const bytes = await readFile("secret.zefer");
const parsed = parseFile(bytes.toString("utf-8"), bytes);
if (parsed) {
console.log(parsed.header.mode); // "text" | "file"
console.log(parsed.header.iterations); // KDF strength
console.log(parsed.header.hint); // public hint (if any)
}Everything the CLI/MCP support is available here. Each lives inside the
encrypted payload (ZeferMeta), invisible without the passphrase.
const buf = await encodeZefer({
content: "top secret",
passphrase: "primary-pass",
fileName: null,
expiresAt: Date.now() + 60 * 60_000, // expires in 60 minutes
// Dual passphrase — two-person authorization
secondPassphrase: "second-pass",
dualKey: true,
// Reveal key — recipients decrypt without the main passphrase (→ ZEFR3)
revealKey: "share-this-key",
// Secret question (answer is PBKDF2-hashed)
question: "Project codename?",
questionAnswer: "bluebird",
// Public, unencrypted metadata
hint: "rotate quarterly",
note: "owned by platform team",
maxAttempts: 5, // 0 = unlimited
allowedIps: ["203.0.113.4", "2001:db8::1"],
iterations: 1_000_000,
});Decrypting a protected file:
const res = await decodeZefer(bytes.toString("utf-8"), "primary-pass", {
rawBytes: bytes,
secondPassphrase: "second-pass", // for dual-key files
questionAnswer: "bluebird", // if the file has a secret question
});The same engine behind zefer keygen and zefer analyze (in src/lib/passwords.ts):
import {
generateValue, generateWithOptions, MODES, CHARSETS,
analyzePassword, crackBucketFor, complianceOf, ATTACK_SCENARIOS,
} from "zefer-cli";
// 7 modes: unicode | secure | alpha | hex | base58 | pin | uuid
generateValue("secure", 64);
generateValue("uuid", 0); // UUID v7 (length ignored)
generateWithOptions("base58", 24, {
excludeAmbiguous: true, // drop 0 O 1 l I
excludeChars: "/\\",
requireAllClasses: true, // guarantee lower/upper/digit/symbol when available
noRepeats: true, // never the same char twice in a row
groupSize: 6, // cosmetic dashes every 6 chars
});
const a = analyzePassword("Tr0ub4dor&3");
a.score; // 0–4 (very weak → strong)
a.effectiveBits; // entropy after structural penalties
a.warnings; // ["common", "sequence", …]For custom pipelines (e.g. envelope encryption, your own container format):
import {
// string helpers (base64 envelope: "salt.iv.ciphertext")
encrypt, decrypt, encryptBytesToBase64, decryptFromBase64,
// raw buffers
encryptRaw, decryptFromBinary,
// chunked (16 MB) — constant active memory per chunk
chunkedEncrypt, chunkedDecryptToBuffer, CHUNK_SIZE,
// KDF & dual-key
deriveKey, combineDualKeys, hashAnswer, benchmarkDevice,
// compression
compressBytes, decompressBytes, smartCompress,
} from "zefer-cli";
const token = await encrypt("hello", "pass", 600_000); // → "salt.iv.ct"
const back = await decrypt(token, "pass", 600_000); // → "hello"Encrypts content (text) or fileData (file) into the .zefer binary
format. Returns the full file as a Buffer.
EncodeOptions (key fields):
| Field | Type | Notes |
|---|---|---|
content |
string? |
Text mode payload |
fileData |
Buffer? |
File mode payload (takes precedence) |
passphrase |
string |
Required |
fileName |
string | null |
Required (use null for text mode) |
expiresAt |
number |
Required. Unix ms, 0 = never |
fileType |
string? |
MIME type (file mode) |
iterations |
number? |
PBKDF2 rounds, default 600_000 |
compression |
"none"|"gzip"|"deflate"|"deflate-raw"? |
default "none" |
secondPassphrase, dualKey |
dual-key (two-person) | |
revealKey |
string? |
produces a ZEFR3 dual-block file |
question, questionAnswer |
secret question | |
hint, note |
public (unencrypted) metadata | |
maxAttempts |
number? |
0 = unlimited |
allowedIps |
string[]? |
IPv4/IPv6 allowlist |
onProgress |
callbacks | optional progress hooks |
type DecodeResult =
| { ok: true; payload: ZeferPayload; header: ZeferHeader }
| { ok: false; error: DecodeError };
type DecodeError =
| "invalid_format" | "wrong_passphrase" | "expired"
| "wrong_answer" | "max_attempts" | "ip_blocked" | "needs_answer";Pass binary bytes via options.rawBytes. options also accepts
secondPassphrase, questionAnswer and onProgress.
Reads the public header (and locates the encrypted blocks) without
decrypting. Returns null if the bytes are not a recognized .zefer file.
- Format:
encodeZefer,decodeZefer,parseFile+ typesZeferHeader,ZeferMeta,ZeferPayload,EncodeOptions,DecodeResult,DecodeError,ParsedFile - Crypto primitives:
encrypt,decrypt,encryptBytesToBase64,decryptFromBase64,encryptRaw,decryptFromBinary,deriveKey,combineDualKeys,hashAnswer,benchmarkDevice,toBase64,fromBase64,encodeText,decodeBytes - Chunked:
chunkedEncrypt,chunkedDecryptToBuffer,CHUNK_SIZE,ChunkedEncryptResult - Compression:
compressBytes,decompressBytes,smartCompress,CompressionMethod - Keygen / analysis:
generateValue,generateWithOptions,entropyOf,charsetFor,MODES,CHARSETS,DEFAULT_GENERATE_OPTIONS,analyzePassword,bucketCrackTime,crackBucketFor,toSuperscript,complianceOf,keyspaceExponent,ATTACK_SCENARIOS,AVERAGE_HUMAN_BITS- types
KeygenMode,GenerateOptions,PasswordAnalysis,PasswordWarning,CrackSpeed,CrackBucket,ScenarioKey,ComplianceCheck
- types
- Attempt counter:
getAttempts,setAttempts,removeAttempts
Memory. encodeZefer/decodeZefer operate in-memory. AES runs in 16 MB
chunks (constant active memory) but the full input and output are held in RAM,
so peak ≈ input + output size. For ~100 MB payloads, give the function
≥ 512 MB. (A true streaming Transform API with constant memory is a
possible future addition — open an issue if you need it.)
Iterations. Always pass an explicit iterations (default 600_000). The
-i 0 auto-benchmark is a CLI-only convenience and is not part of the
library path, so there is no per-invocation PBKDF2 calibration cost on cold
start.
Attempt counter. When a file sets maxAttempts, decodeZefer reads/writes
~/.zefer/attempts.json. On read-only or ephemeral filesystems this is a
non-fatal no-op (write failures are swallowed).
No network, no telemetry. Everything runs locally; passphrases never leave the process.
A file produced by any channel decrypts in every other:
// File written by `zefer encrypt secret.txt -p pass` (CLI)
const bytes = await readFile("secret.txt.zefer");
const res = await decodeZefer(bytes.toString("utf-8"), "pass", { rawBytes: bytes });
// res.payload.fileData → original bytesThe smoke-test suite (npm test) asserts this round-trip across CLI ⇄ library
and ESM ⇄ CJS on every build.