Package Exports
- @nikip0/colorless
Readme
colorless (JavaScript / TypeScript)
Authorize, gate, and prove every action your AI agent takes. Tamper-evident. Zero dependencies. First-class TypeScript types. The JS/TS port of colorless.
npm install @nikip0/colorless # zero dependencies (uses only Node built-ins; Node >= 18)import { Colorless } from "@nikip0/colorless";
const cl = new Colorless({ ledger: "agent.jsonl", onApproval: pingSlack });
cl.deny("delete_database"); // never
cl.requireApproval("refund", (a) => a.args.amount > 100); // big ones need a human
const refund = cl.guard(async ({ amount, to }) => pay(amount, to), { name: "refund" });
await refund({ amount: 80, to: "cust_12" }); // runs — sealed in the chain
await refund({ amount: 5000, to: "cust_12" }); // throws ApprovalRequired until a human says yes
cl.verify(); // { ok: true, length: 412, head: "9f3c…" } — proof nothing was alteredTool calls (OpenAI / Anthropic / MCP)
import { Colorless, ToolGuard, PolicyDenied } from "@nikip0/colorless";
const cl = new Colorless({ ledger: "agent.jsonl" });
cl.deny("delete_repo");
const tg = new ToolGuard(cl);
tg.add("search_web", searchWeb);
tg.add("send_invoice", sendInvoice);
for (const call of llmResponse.toolCalls) {
try {
const result = await tg.call(call.name, call.arguments); // gated + sealed
} catch (e) {
if (e instanceof PolicyDenied) /* hand the refusal back to the model */;
}
}Same ledger, any language
This SDK writes the exact same JSONL hash-chain format as the Python engine, so a ledger an agent writes in Node can be verified from the terminal with the Python CLI:
colorless verify agent.jsonl # ✓ verifies a Node-written ledger (any JSON value; see note below)Cross-language verify covers strings (incl. non-ASCII/emoji), integers, booleans, null, and ordinary decimals. The one edge: floats that serialize in scientific notation (e.g.
1e-7, very small/large) format differently in Python vs JS — avoid those in payloads you verify across languages.
API
new Colorless({ ledger, policy, onApproval, redact })—redactdefaults to"auto"(secrets masked); passnullto disable..deny / .allow / .requireApproval(name?, when?, reason?)— ordered rules, first match wins..guard(fn, { name })— wrap a tool (sync or async)..run(name, args, fn)— gate a call directly..verify()·.head()·.entries(ref?)·.anchor(path)·.verifyAgainstAnchor(path)..subscribe(cb)— firecb(entry)after every sealed action (build alerts/exporters).onApprovalmay return{ approved, approver }to seal who authorized it.ToolGuard(cl).add(name, fn).call(name, args)— gate + seal one tool call.
MIT © 2026 Niki Petrov