npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

toolwrit

v0.4.0

Published

A written authority for AI agents. Allowlist the tools, cap the budget, prove what happened.

Readme

toolwrit

A written authority for AI agents. Allowlist the tools, cap the budget, prove what happened.

This is the TypeScript package. The Python port publishes under the same name on PyPI and writes a compatible audit chain — a log from either verifies with the other.

Full documentation, the threat model and the policy reference live in the repository: https://github.com/emektor/toolwrit

60-second start

npm install toolwrit

The package installs a binary of the same name, toolwrit. The Python port speaks the same policy language and writes the same chain; see docs/python.md.

toolwrit.yaml

version: "1"
name: support-agent
default: deny

budget:
  calls: 50
  usd: 2.00

rules:
  - id: read-workspace
    tools: ["fs.read", "fs.list"]
    effect: allow
    when:
      path:
        startsWith: ["/srv/workspace/"]
        excludes: [".."]

  - id: no-secrets
    tools: ["fs.*"]
    effect: deny
    when:
      path:
        matches: "\\.env|id_rsa"

The three-line code change. Wherever you currently execute a tool call, wrap it:

import { Toolwrit, ToolwritDenied, loadPolicyFile } from 'toolwrit';

const toolwrit = new Toolwrit({
  policy: loadPolicyFile('./toolwrit.yaml'),
  auditFile: './audit.jsonl',
});

// before:  const result = await tools[name](args);
// after:
const result = await toolwrit.guard(name, args, () => tools[name](args));

That is the whole integration. guard evaluates the call, writes the decision to the audit chain, and only then invokes your handler. A refusal throws ToolwritDenied — never a silently skipped side effect:

try {
  return await toolwrit.guard(name, args, () => tools[name](args));
} catch (err) {
  if (err instanceof ToolwritDenied) {
    // Hand the boundary back to the model so it can adapt.
    return { isError: true, content: [{ type: 'text', text: err.message }] };
  }
  throw err;
}

Metering is explicit, because the pricing assumption should be visible in your code rather than guessed by ours:

toolwrit.meter(inputTokens, outputTokens, { input: 0.003, output: 0.015 }); // USD per 1k tokens
toolwrit.spend(0.02);                                                       // non-token spend

And at the end of the run:

console.log(toolwrit.usage()); // { calls: 1, tokens: 1500, usd: 0.0281, bytes: 512, startedAt: 1789035198799 }
console.log(toolwrit.head());  // a 64-char sha256 — the head of this run's audit chain

The MCP one-liner

Put it in front of an MCP server you already run, without touching that server's code:

toolwrit run --policy toolwrit.yaml -- node my-mcp-server.js

Every tools/call is checked before it is forwarded. A refusal comes back to the agent as a tool error naming the constraint that failed, so the model can read the boundary and adapt, rather than as a protocol error that would kill its turn.

What it does not do

It governs the tool boundary. It does not read prompts or model output, it is not a network egress filter, and it cannot survive its own host being compromised. The audit log is tamper-evident, not tamper-proof: anchor the head hash somewhere the agent cannot reach, or it proves nothing.

The full list, including the findings from two independent security reviews and the one that is still open, is in SECURITY-REVIEW.md.

Licence

Apache-2.0.