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

@botiverse/porthole

v0.5.1

Published

A host-side sandbox for agent-made HTML: isolation, egress hardening, a themed shell, gated host functions, and the agent manual, from one config.

Readme

Porthole

@botiverse/porthole: show HTML an agent wrote (a widget, a whole page, an MCP Apps view) inside your app, safely. One config gives the frame its isolation, its CSP, a themed shell, the host functions it may call (each behind a gate), and the manual your agent reads.

npm install @botiverse/porthole

Node 24 or later, or Cloudflare Workers. The browser part is plain ES modules with no dependencies (bundle it, or serve dist/ with an import map: docs/ADOPTING.md § 4).

Two ways to show content

  • Opaque, in the page. No server needed: content runs in a sandboxed frame with an opaque origin. For widgets in a conversation.
  • Served, on an origin of its own. Each page, or each conversation's widgets, gets an origin like a-<id>.example-usercontent.com, so it can keep storage, open at a clean address in a new tab, and be framed by your app only. The host kit (createContentHost) is the server side; your app writes only its policy: which pages exist and who may see them.

Try it in two minutes

The package ships a whole app, the same code on Node and on Workers:

PORTHOLE_SECRET=$(npx porthole secret) node node_modules/@botiverse/porthole/examples/node/server.mjs
# open http://localhost:8790: a stored page, served on http://a-hello.content.localhost:8790
  • examples/app.mjs: the app (policy, routes, page), runtime-agnostic.
  • examples/node/server.mjs (nodeServer), examples/workers/ (contentHostFor, with its D1 migration and wrangler.jsonc).
  • examples/conformance/: Porthole's conformance suite run against it, on three engines.
  • examples/mcp/server.mjs: the agent's tools (show, guide, check) as an MCP server.

The smallest opaque display

import { createHost, presets } from "@botiverse/porthole";

const host = createHost(presets.inlineWidget());
const display = host.mount(document.getElementById("widget"), {
  displayId: "w1",
  title: "A chart",
  scheme: "light",
});
display.setContent("<p>Hello from an agent</p>");

In React: <PortholeProvider> around <PortholeFrame config displayId title scheme content /> from @botiverse/porthole/react.

What the agent gets

import { admitContent, checkTool, guideTool, showTool } from "@botiverse/porthole";

const tools = [showTool(config), guideTool(config), checkTool(config)]; // name, description, JSON Schema
const admitted = admitContent(config, html); // refuses only what certainly fails, saying what to change

checkPage (@botiverse/porthole/check) runs a page in the real sandbox at phone and desktop width, light and dark, before anyone sees it, and tells the agent what broke in a few lines. examples/mcp/server.mjs wires the tools as an MCP server.

Read next

  • docs/ADOPTING.md: getting started, step by step: the server, the page, its CSP, Workers, local development, the agent's tools, conformance, troubleshooting.
  • Choosing egress: what content may load (closed, library CDNs, passive media from any https site), what each level opens and the risk it re-opens.
  • docs/DESIGN.md: how it works. docs/DECISIONS.md: why, by P-number (the code cites them).

Subpaths

| Import | What | | --- | --- | | @botiverse/porthole | createHost, presets, defineDisplays, portholeClient, lintContent, the tools (browser and server) | | @botiverse/porthole/react | PortholeProvider, PortholeFrame, PortholeView | | @botiverse/porthole/server | createContentHost (routes), contentHostFor, prefixedName, d1TicketStore (any runtime) | | @botiverse/porthole/node | nodeServer, nodeHost, nodeProbe (Node's http server) | | @botiverse/porthole/mcp | the MCP Apps adapter | | @botiverse/porthole/check | checkPage, playwrightDriver, cdpDriver: the agent's pre-show check | | @botiverse/porthole/conformance | defineHostConformance: the suite, against your app (Playwright) |

npx porthole secret makes a secret; npx porthole d1-schema prints the D1 migration.

Apache-2.0. /server also serves copies of Chart.js 4.5.1 (MIT) and D3 7.9.0 (ISC) to live widgets (P079), each with its license beside it (/__porthole/lib/<name>@<version>/), from src/libraries/files.generated.ts.