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

@shift-labs/pi-rlm

v0.5.0

Published

A pi extension with a single tool: execute, running TypeScript in a persistent Bun evaluator

Readme

pi-rlm

A pi extension that replaces the usual toolbox with a single tool: execute, which runs TypeScript in a persistent Bun evaluator.

Everything an agent would normally reach for a separate tool to do — reading files, running shell commands, editing, searching, delegating to subagents — is expressed as code inside that one tool.

 ✓ rlm · shell · const files = (await Bun.$`ls -1`.text()).split("\n") · ↑ 2 ↓ 7 lines · 41ms
 ✓ rlm · const tests = files.filter((f) => f.includes("test")) · ↑ 1 ↓ 1 lines · 3ms

The second cell reuses the first cell's variable. Nothing was re-read, and nothing was re-parsed from text — because the evaluator is still there.

Why one tool

A fixed set of tools is a fixed vocabulary. Every new capability means a new tool, a new schema, and a model that has to be taught when to reach for it.

Here the vocabulary is a programming language. Capabilities arrive as functions in the evaluator's namespace rather than as entries in a tool list, so the interface the model sees never changes while what it can do keeps growing. It also changes how an agent works: intermediate results live in variables instead of being re-derived from earlier output, so a long task compounds rather than repeating itself.

What the agent gets

Subagents as a call stack. A cell that calls rlm.run renders its children as stack frames beneath it — glyph, name, status, age, spawn site — nested by depth. While anything runs the stack stays visible on the collapsed cell; once every frame settles it folds into a header chip (3 subagents · 1 failed). Frame records live on disk beside each child's output, so stacks survive session resume and read truthfully post-mortem.

A namespace that persists. Variables, functions, classes, and imports stay available across calls, across turns, and — on a best-effort basis — across session resumes. Whatever cannot be serialised is named in the restore report rather than silently dropped. Long sessions stay cheap: snapshots re-serialise only what changed, large long-untouched values revive lazily (they load the first time they are read), and rlm.forget("name") is the one true delete — the engine never discards agent state on its own.

Shell as values, not text. await Bun.$git log --oneline.quiet() returns an object with an exit code and captured output, ready to be assigned and filtered. No parsing a transcript to recover what a command said.

Subagents as function calls. await rlm.run("task") spawns a real child agent and returns a handle at admission. Children write their answers to files; the parent polls the registry and reads them when it wants. Delegation happens mid-computation instead of as a separate mode.

Cancellation that costs one cell. Interrupting a running cell leaves the namespace intact, and the cancelled cell cannot keep writing to it afterwards.

Install

pi install npm:@shift-labs/pi-rlm

Bun is required. pi itself runs on Node, but the evaluator is a Bun process — without it on your PATH the engine will tell you so and stop.

curl -fsSL https://bun.sh/install | bash

Launch

The extension is dormant until asked for. A plain pi session is untouched — default prompt, default tools, no evaluator. Activation is one flag:

pi --rlm

That collapses the tool surface to execute and replaces the system prompt; no other pi flags are needed. To verify the two worlds:

pi -p "what tools do you have?"          # stock pi: read, bash, edit, ...
pi -p --rlm "what tools do you have?"    # one tool: execute

To run from a clone (development), load the extension explicitly — the flag works the same, or set PI_RLM_FORCE=1 where flag plumbing is awkward:

git clone https://github.com/shift-labs-ai/pi-rlm && cd pi-rlm
bun install
pi --rlm -e ./src/extension/index.ts

Configuration

| Variable | Default | Meaning | | --- | --- | --- | | PI_RLM_SUBAGENT_MODEL | anthropic/haiku | Model children are spawned with | | PI_RLM_MAX_DEPTH | 2 | How deep recursive delegation may go | | PI_RLM_DEPTH | 0 | Depth of the current agent; set on children automatically | | PI_RLM_NPM_CACHE_DIR | ~/.cache/pi-rlm-npm | Where npm: imports install |

Session state lives in .pi-rlm/<session>/: the namespace snapshot and each subagent's session file and output.

npm imports

Static top-level imports can name npm packages directly:

import { z } from "npm:zod@4";
import { format } from "npm:date-fns@4/format";

The first import of a name@version installs it into an isolated cache directory — the working directory's node_modules is never touched — and the binding persists across cells like any other import. Pin versions when repeatability matters: an unpinned name means latest, resolved once at first install and reused from the cache after that. Specifiers are code-execution input: importing a package runs its code. Dynamic import("npm:...") is not supported.

How it works

The extension runs a Bun child process that owns the namespace. Cells are transformed so their top-level declarations become namespace assignments, then executed inside a with block over a proxy. Host and guest talk over a private pipe with authenticated framing, which is what stops a cell from being able to report its own outcome.

ARCHITECTURE.md covers the design and the reasoning behind it.

Development

bun run check      # typecheck, lint, and the full suite — the gate
bun test           # tests only
bun run typecheck  # tsc --noEmit
bun run format     # biome

The test suite is the specification. test/engine.contract.test.ts states each guarantee the evaluator makes and why it exists; read it before changing engine behaviour, and never weaken a case to make a change pass.

Layout

src/engine/      the evaluator
  index.ts       EngineManager — host side: lifecycle, queueing, output, snapshots
  guest.ts       the Bun process that owns the namespace and runs cells
  protocol.ts    typed, authenticated framing between the two
  transform.ts   cell source → executable body
  npm.ts         lazy, isolated installs behind npm: imports
src/extension/   the pi integration
  index.ts       tool registration, session wiring
  prompt.ts      the system prompt
  subagents.ts   spawning, registry, file-based results
  render-core.ts cell layout (pure)
  render.ts      binds pi's theme and width primitives to it