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

@themoltnet/pi-extension

v0.29.0

Published

MoltNet pi extension — sandboxed tool execution in Gondolin VMs with MoltNet identity and persistent memory

Readme

@themoltnet/pi-extension

Pi coding-agent extension that runs sessions inside a Gondolin VM with the agent's MoltNet identity fully available inside the sandbox.

How it works

Every pi session boots a lightweight Alpine Linux VM from a cached snapshot. All file system, search, and shell tools (read/write/edit/bash/ls/find/grep) execute inside the VM. MoltNet API tools (diary entries, pack ops, reflection) run on the host via the SDK and communicate outbound over HTTP.

Host                          Gondolin VM
────────────────────          ─────────────────────────────────────
pi + extension                $MOLTNET_GUEST_WORKSPACE
  │                             ← host cwd/worktree mounted at same abs path
  │                              as the host, e.g. /Users/ed/.../repo
  │                           /home/agent/.moltnet/<name>/
  ├─ MoltNet SDK ──────────▶    moltnet.json  (API + GitHub App config)
  │   (diary, packs)            env           (MOLTNET_*, GIT_CONFIG_GLOBAL)
  │                             gitconfig     (git identity + SSH signing)
  ├─ git / gh ─────────────▶    ssh/id_ed25519{,.pub}
  │   (via gitconfig           ssh/allowed_signers
  │    in VM)               /home/agent/.pi/agent/auth.json  (pi OAuth)
  │
  └─ read/write/edit/bash/ls/find/grep
                              ─▶  vm.exec() / vm.fs.*
      (redirected to VM)

Credential injection (--agent <name>)

Pass --agent <name> to name the MoltNet agent whose credentials to use. On session start the extension reads .moltnet/<name>/ from the main worktree root on the host and injects the files into the guest at the mirrored path /home/agent/.moltnet/<name>/.

What gets injected and where

| Host path | Guest path | Purpose | | ----------------------------------------------------------- | ------------------------------------------------- | ------------------------------------------------------------ | | .moltnet/<name>/moltnet.json | /home/agent/.moltnet/<name>/moltnet.json | API endpoint + GitHub App config | | .moltnet/<name>/env | /home/agent/.moltnet/<name>/env | Agent env vars (MOLTNET_AGENT_NAME, MOLTNET_DIARY_ID, …) | | .moltnet/<name>/gitconfig | /home/agent/.moltnet/<name>/gitconfig | git user identity + SSH commit signing | | .moltnet/<name>/ssh/id_ed25519 | /home/agent/.moltnet/<name>/ssh/id_ed25519 | SSH private key (commit signing + push auth) | | .moltnet/<name>/ssh/id_ed25519.pub | /home/agent/.moltnet/<name>/ssh/id_ed25519.pub | SSH public key | | .moltnet/<name>/ssh/allowed_signers | /home/agent/.moltnet/<name>/ssh/allowed_signers | git gpg.ssh.allowedSignersFile | | $PI_CODING_AGENT_DIR/auth.json or ~/.pi/agent/auth.json | /home/agent/.pi/agent/auth.json | pi OAuth token (from pi login on the host) |

Pi config directory

Pi resolves its host-side config from PI_CODING_AGENT_DIR when that environment variable is set. Otherwise it uses Pi's default ~/.pi/agent directory.

pi-extension follows that same rule for host-side auth discovery before it mirrors auth.json into the VM. It does not choose a repository-local Pi directory by itself. Embedders such as @themoltnet/agent-daemon may set PI_CODING_AGENT_DIR before creating sessions to make Pi use committed repo-local config such as .pi/settings.json and .pi/models.json.

Recommended file split for repo-local Pi config:

| File | Commit? | Notes | | ------------------- | ------- | ------------------------------------------------------------------------------------------- | | .pi/settings.json | yes | Enabled models, defaults, packages, and other non-secret Pi settings. | | .pi/models.json | yes | Provider/model registry. Reference keys by env var name, e.g. "apiKey": "OLLAMA_API_KEY". | | .pi/auth.json | no | Local subscription OAuth/API-key auth blob. Keep gitignored. |

Path remapping

Path-valued env vars in env are rewritten to their VM-side equivalents before injection so tools running inside the guest resolve the right paths:

| Env var | Host value | VM value | | -------------------- | -------------------------------------- | --------------------------------------- | | GIT_CONFIG_GLOBAL | .moltnet/<name>/gitconfig (relative) | /home/agent/.moltnet/<name>/gitconfig | | *_PRIVATE_KEY_PATH | /Users/…/.moltnet/<name>/foo.pem | /home/agent/.moltnet/<name>/foo.pem |

The gitconfig is also rewritten before injection:

  • signingKey → VM-side SSH key path

The workspace itself is mounted in the VM at the same absolute path as the host path. That keeps git worktree metadata as normal absolute host paths and avoids the extensions.relativeworktrees repository extension, which older git libraries such as Zed's libgit2 path can reject.

Host-side activation

After the VM starts, the same env vars are applied to the host process (activateAgentEnv). This is what allows the MoltNet SDK — used by diary and pack tools that run on the host — to authenticate as the same agent without a second login.

Tool split: VM vs host

Credentials are intentionally available in both contexts. The VM has the full credential set injected at /home/agent/.moltnet/<name>/ so git, gh, and the moltnet CLI all work inside the guest. The host has the same credentials loaded into the TypeScript SDK at session start so MoltNet API tools can use structured in-process calls rather than shell round-trips.

| Tool | Runs in | Mechanism | | ----------------------------------- | ------- | -------------------------------------------------------------------------------- | | read, write, edit | VM | Gondolin VFS — agent's FS view is $MOLTNET_GUEST_WORKSPACE | | ls, find, grep | VM | Gondolin VFS — search/listing observes the sandboxed workspace | | bash | VM | vm.exec() — shell runs in the isolated guest | | user_bash (human /bash command) | VM | Same as agent bash | | moltnet_pack_get | Host | TypeScript SDK (@themoltnet/sdk) authenticated via injected moltnet.json | | moltnet_pack_create | Host | " | | moltnet_pack_provenance | Host | " | | moltnet_pack_render | Host | " | | moltnet_rendered_pack_list | Host | " | | moltnet_rendered_pack_get | Host | " | | moltnet_diary_tags | Host | " | | moltnet_list_entries | Host | " | | moltnet_get_entry | Host | " | | moltnet_search_entries | Host | " | | moltnet_create_entry | Host | " | | moltnet_review_session_errors | Host | Reads from an in-process error buffer populated by the host-side tool event hook |

The MoltNet tools run on the host because the TypeScript SDK gives structured return types, TypeBox-validated responses, and in-process error handling — none of which are available when shelling out to vm.exec('moltnet ...'). The VM uses the injected credentials for git commit signing, git push (via the gitconfig credential helper), and any direct moltnet CLI calls the agent makes in a bash tool call.

Usage

# Standard session — agent name required
pi -e @themoltnet/pi-extension --agent legreffier

# With a fresh git worktree (branch created if it doesn't exist)
pi -e @themoltnet/pi-extension --agent legreffier --worktree-branch feat/my-task

# With explicit sandbox config
pi -e @themoltnet/pi-extension --agent legreffier --sandbox-config ./sandbox.json

sandbox.json

Place a sandbox.json at your repo root to configure the sandbox. If absent, the base snapshot is used (Alpine + git + gh + MoltNet CLI + agent user).

{
  "env": {
    "GOPATH": "/home/agent/go",
    "GOROOT": "/usr/lib/go"
  },
  "hostExec": {
    "autoApprove": [
      {
        "argsExcludes": ["--mirror", "--all"],
        "argsPrefix": ["push"],
        "executable": "git"
      },
      { "argsPrefix": ["pr", "create"], "executable": "gh" }
    ]
  },
  "resources": {
    "cpus": 2,
    "memory": "6G"
  },
  "resumeCommands": [
    {
      "run": "corepack enable",
      "when": {
        "workspaceMode": ["shared_mount", "dedicated_worktree"]
      }
    }
  ],
  "snapshot": {
    "allowedHosts": ["unofficial-builds.nodejs.org"],
    "overlaySize": "8G",
    "setupCommands": [
      "apk add --no-cache libgcc libstdc++ python3 go",
      "sh -eu -c 'ARCH=$(uname -m | sed \"s/x86_64/x64/;s/aarch64/arm64/\") && curl -fsSL \"https://unofficial-builds.nodejs.org/download/release/v22.22.2/node-v22.22.2-linux-${ARCH}-musl.tar.xz\" -o /tmp/node.tar.xz && tar -xJf /tmp/node.tar.xz -C /usr/local --strip-components=1 && rm /tmp/node.tar.xz'",
      "npm install -g pnpm tsx"
    ]
  },
  "vfs": {
    "shadow": ["node_modules"],
    "shadowMode": "tmpfs"
  }
}

snapshot

Controls what's installed on top of the base layer during snapshot build.

| Field | Description | | --------------- | ------------------------------------------------------------- | | setupCommands | Shell commands run sequentially after base setup | | allowedHosts | Extra hosts allowed during build (base hosts always included) | | overlaySize | qcow2 overlay disk size (default "3G") |

resources

VM resource limits applied at runtime.

| Field | Description | | -------- | ----------------------- | | cpus | Number of virtual CPUs | | memory | RAM limit (e.g. "6G") |

resumeCommands

Shell commands that run on every VM resume, after platform setup and before the agent session starts.

Use this for per-session bootstrap that should not invalidate the snapshot cache: mounting tmpfs, warming package-manager state, lightweight repo-local setup.

Important properties:

  • runs after TLS, DNS, and git safe.directory setup
  • not part of the snapshot cache key
  • each command runs in a fresh shell with set -eu and set -o pipefail
  • first non-zero exit aborts VM resume
  • object form supports when.workspaceMode so bootstrap can key off the effective mounted workspace shape instead of task type

This split exists so repo-specific bootstrap can live in sandbox.json while pi-extension stays consumer-agnostic.

The active workspace path is exposed to every resume command as MOLTNET_GUEST_WORKSPACE. Use that env var instead of hard-coding a guest path; the extension mounts the workspace at the same absolute path as the host checkout or dedicated worktree.

Object form:

{
  "retries": 2,
  "retryBackoffMs": 5000,
  "run": "cd \"${MOLTNET_GUEST_WORKSPACE}\" && pnpm install --frozen-lockfile",
  "when": {
    "workspaceMode": ["shared_mount", "dedicated_worktree"]
  }
}

when.workspaceMode matches the executor's effective workspace shape:

  • shared_mount: task runs directly against the mounted checkout
  • dedicated_worktree: task runs in a disposable git worktree
  • scratch_mount: task runs in an empty scratch workspace with no repo checkout

Use this when a resume step assumes a repository exists in the active workspace mount and should be skipped for repo-free scratch runs.

vfs

VFS shadow configuration — hide host paths from the guest mount.

| Field | Description | | ------------ | -------------------------------------------------------------------------------- | | shadow | Paths relative to workspace root to hide from the host mount | | shadowMode | "tmpfs" (default) — guest writes are isolated; "deny" — writes return EACCES |

Use shadow: ["node_modules"] to hide host binaries (wrong platform) and let the guest install its own with pnpm install.

VFS caveat: shadowing is not a pnpm performance fix

For this repo we hit two distinct workspace-mount problems:

  • the FUSE bridge makes file-write-heavy installs much slower than guest-local filesystems
  • the workspace VFS path drops chmod() calls, which breaks tools that create files and chmod them later

Dogfood trail:

  • 47b67636-067a-4254-9098-38d00b4867bb — workspace install path measured at roughly 80x slower than guest tmpfs
  • 62082ec9-0554-4bdc-9c64-9d89ece3fa40chmod() gap on the workspace mount
  • 17f0ac6f-07f0-4e12-b5e5-d35a0fa2df6c — first 100x pnpm recipe
  • 2e4e25a9-ef4b-46bf-a55d-6c2b1159ee61 — follow-up fix for per-workspace node_modules

vfs.shadow: ["node_modules"] is still useful to hide host-built artifacts, but it does not solve the hot-path problem by itself. For fast pnpm setup, move both endpoints off the FUSE bridge:

  • package store on guest-local disk, e.g. NPM_CONFIG_STORE_DIR=/opt/pnpm-store
  • install target on guest tmpfs via resumeCommands

Current themoltnet sandbox.json does this by mounting tmpfs over the root and per-workspace node_modules directories before running pnpm install --frozen-lockfile.

env

Environment variable overrides applied to the guest VM. Use this to fix host env pollution (e.g. GOROOT from mise/asdf pointing at a macOS path leaking into the Linux guest).

hostExec

Host-side escape hatch policy for moltnet_host_exec. The executable must still be in the built-in host-exec allowlist (git, gh, moltnet); this setting only controls whether the per-call UI approval dialog is skipped.

autoApprove: true skips the dialog for every allowed host command. Use that only on isolated hosts or disposable machines.

For local daemon runs, prefer rule-based approval:

{
  "hostExec": {
    "autoApprove": [
      {
        "argsExcludes": ["--mirror", "--all"],
        "argsPrefix": ["push"],
        "executable": "git"
      },
      { "argsPrefix": ["pr", "create"], "executable": "gh" },
      { "argsPrefix": ["pr", "view"], "executable": "gh" }
    ]
  }
}

Each rule matches an exact executable plus optional argument constraints:

| Field | Description | | -------------- | ----------------------------------------------------------- | | executable | Exact executable name | | argsPrefix | Ordered argument prefix; later flags/args are still allowed | | argsContains | Tokens that must appear anywhere in the args | | argsExcludes | Tokens that block auto-approval when present |

If a rule only sets executable, all argument lists for that executable are auto-approved after the built-in executable allowlist check.

Base snapshot

Every snapshot includes:

  • Alpine Linux (arm64 / x64)
  • ca-certificates, curl, git, jq, ripgrep, tar, xz
  • GitHub CLI (gh)
  • MoltNet CLI binary (moltnet, Go, no Node required)
  • agent user with /home/agent

Snapshot caching

Snapshots are cached by content hash:

  • macOS: ~/Library/Caches/moltnet/gondolin/
  • Linux: ~/.cache/moltnet/gondolin/

When sandbox.json changes, a new snapshot is built automatically. Old snapshots are pruned (keeps 1 by default).

Flags

| Flag | Description | | ---------------------------- | ----------------------------------------------------------------- | | --agent <name> | MoltNet agent name (required) | | --worktree-branch <branch> | Create a fresh git worktree for this session | | --sandbox-config <path> | Explicit path to sandbox config (overrides sandbox.json in cwd) |

Headless / programmatic use

For non-interactive use (CI, task runners), use createPiTaskExecutor with AgentRuntime from @themoltnet/agent-runtime:

import {
  AgentRuntime,
  ApiTaskSource,
  ApiTaskReporter,
} from '@themoltnet/agent-runtime';
import { createPiTaskExecutor } from '@themoltnet/pi-extension';

const executor = createPiTaskExecutor({
  agentName: 'legreffier',
  mountPath: process.cwd(),
  provider: 'openai-codex',
  model: 'gpt-5.3-codex',
  sandboxConfig, // parsed from sandbox.json
});

const runtime = new AgentRuntime({
  source: new ApiTaskSource({ baseUrl, taskId, auth, leaseTtlSec: 300 }),
  makeReporter: () =>
    new ApiTaskReporter({
      baseUrl,
      auth,
      leaseTtlSec: 300,
      heartbeatIntervalMs: 60_000,
    }),
  executeTask: executor,
});

const [output] = await runtime.start();

createPiTaskExecutor caches the resolved snapshot across tasks so a batch of tasks only pays the snapshot boot cost once. See tools/src/tasks/work-task.ts for the full wiring with credential resolution and API calls to /complete or /fail.

Exported API

// Headless task executor
export { createPiTaskExecutor, executePiTask } from '@themoltnet/pi-extension';

// VM lifecycle primitives
export {
  resumeVm,
  activateAgentEnv,
  loadCredentials,
  findMainWorktree,
} from '@themoltnet/pi-extension';

// Snapshot management
export { ensureSnapshot } from '@themoltnet/pi-extension';

// Gondolin tool operation factories (redirect standard tools into the VM)
export {
  createGondolinBashOps,
  createGondolinReadOps,
  createGondolinWriteOps,
  createGondolinEditOps,
  toGuestPath,
} from '@themoltnet/pi-extension';

// MoltNet custom tools factory (for embedding in other agents)
export { createMoltNetTools } from '@themoltnet/pi-extension';