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

interpretthis

v0.4.1

Published

Sandboxed Python interpreter for untrusted and LLM-generated code

Readme

interpretthis

Run untrusted or LLM-generated Python inside a sandbox — from Node.

interpretthis evaluates a Python AST under resource limits and an allowlisted language surface. It is not CPython in a subprocess and not a container: the interpreter is written in Rust and simply has no filesystem, network, or process access to give away. Capabilities reach the script only through tools you inject.

npm install interpretthis

Prebuilt binaries ship for macOS, Linux (glibc and musl), and Windows, on x64 and arm64. Node 22+.

Why

You want a model to write Python that transforms data, scores records, or orchestrates your tools — without giving it a real Python process. This is the evaluator: you own the tools, the limits, and what happens to the result.

Quick start

import { Interpreter } from 'interpretthis'

const interp = new Interpreter({
  double: ({ n }) => n * 2,
})

const result = await interp.execute('answer = double(n=x)\nprint(answer)', { x: 21 })

console.log(result.stdout)              // '42\n'
console.log(interp.getVariable('answer')) // 42

Failure is data, not a thrown error. execute resolves to a result carrying stdout and error, because a script that prints three lines and then throws has told you something useful in both halves — and that pair is exactly what you feed back to a model:

const result = await interp.execute("print('working...')\nresult = 1 / 0")

result.ok            // false
result.stdout        // 'working...\n'
result.error.kind    // 'exception'
result.error.message // 'ZeroDivisionError: division by zero (at line 2)'

error.kind is a stable string you can switch on: syntax, security, runtime, limitExceeded, recursionLimit, tool, name, type, value, attribute, assertion, exception, stateFormat.

Tools

A tool is any function — sync or async. The script calls it by name; arguments arrive as an object.

const interp = new Interpreter({
  fetch: async ({ url }) => {
    const response = await fetch(url)
    return response.text()
  },
})

await interp.execute("page = fetch(url='https://example.com')")

execute is asynchronous by necessity, not by style: a JS tool callback can only resolve while the event loop is free, so the run happens off-thread and the loop stays available to service your tools.

Tools that are safe to overlap can say so, and the interpreter will run them concurrently:

const interp = new Interpreter({
  fetch: { func: fetchPage, parallelizable: true },
})

// These three overlap rather than running end to end.
await interp.execute(`
a = fetch(url='https://example.com/1')
b = fetch(url='https://example.com/2')
c = fetch(url='https://example.com/3')
print(a + b + c)
`)

A tool that throws becomes a catchable Exception inside the script, and an error.kind === 'tool' result for you if the script does not catch it.

Limits

const interp = new Interpreter(null, {
  maxOperations: 1_000_000,
  maxMemoryBytes: 64 * 1024 * 1024,
  maxExecutionTime: 5, // seconds
  maxRecursionDepth: 100,
})

Exceeding one stops the script with error.kind === 'limitExceeded' — with whatever it printed before then still in stdout.

Types across the boundary

Most things map to the obvious counterpart. Two asymmetries are worth knowing, because they are deliberate rather than accidental:

  • A JS array always becomes a Python list, never a tuple — JavaScript has no tuple to distinguish. A Python tuple arrives here as a frozen array.
  • A sandbox integer arrives as a number when it fits, and a bigint when it exceeds Number.MAX_SAFE_INTEGER. Silently losing digits would be worse.

Python bytes ↔ Buffer, set ↔ Set, datetime ↔ Date. A dict becomes a plain object when every key is a string, and a Map otherwise — an object would turn {1: 'a'} into {'1': 'a'} and lose the key's type.

Values crossing the boundary are copied, not aliased: mutations the script makes are not visible on your objects. Read results back with getVariable.

Anything with no counterpart — a function, class, or instance defined inside the sandbox — throws a TypeError naming the type rather than quietly becoming null.

Resumable state

const blob = interp.exportState() // Buffer; sign or encrypt it yourself

const later = new Interpreter()
later.importState(blob)
await later.execute('counter = counter + 1')

What it is not

  • Not full CPython. A large subset, deliberately chosen. See CONFORMANCE.md.
  • Tools are trusted. A tool with side effects extends the trust boundary by exactly as much as it does. That is the point, and it is your call.
  • async/await inside the script is not supported. Await around execute, not within it.

Security boundary: THREAT_MODEL.md.

Licence

MIT OR Apache-2.0, at your option.

The prebuilt native binaries statically link malachite (LGPL-3.0-only), reached transitively via the Python parser. See the bundled NOTICE.