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

@pensieve/cli

v0.8.0

Published

The backend for your agent — authorize a machine to act as an agent on a Pensieve server, then call its tools from scripts and harnesses as typed TypeScript.

Readme

@pensieve/cli

The backend for your agent — authorize a machine to act as an agent on a Pensieve server, then call its tools from scripts and harnesses as typed TypeScript.

npm install -g @pensieve/cli

Commands

pensieve setup [--server <url>] [--no-browser]

Make the current directory a Pensieve project, in one step: write .pensieverc.json, add the store to .gitignore, run connect, and run sdk sync. Every step is skipped when already satisfied, so re-running reports the current state instead of redoing work.

pensieve status

What this machine can do right now: CLI and Node versions, which config was resolved and from where, whether the server answers, which agents are connected, and where the SDK entry file is. Works with the server down — an unreachable server is reported, not thrown.

pensieve run <file.ts> [args…]

Run a TypeScript file with @pensieve/sdk resolved. The generated SDK declares services as namespaces, which node --experimental-strip-types refuses, so the CLI ships its own TypeScript runner — the consuming directory needs no package.json and no node_modules.

pensieve connect [--no-browser]

Authorize this machine to act as an agent. Opens the browser, where you sign in and pick which of your agents this machine acts as, receives the token over a local loopback callback, and saves it to the store's config.json (mode 0600).

pensieve sdk sync [--agent <id>] [--no-link]

Generate this agent's tools as a TypeScript SDK and link it as node_modules/@pensieve/sdk in the current directory. Prints the entry file, which lists every service the workspace has. Re-run whenever the workspace's tools change.

pensieve files <upload|download> <source> <destination> [--dry-run] [--concurrency <n>]

Copy a file or a whole directory between the local disk and this workspace's drive:

pensieve files upload   ./reports        reports/         # local -> drive, recursively
pensieve files download reports/         ./reports        # drive -> local, recursively
pensieve files upload   ./notes.md       reports/notes.md # one file

A directory source is walked recursively. Nothing is deleted at the destination — extras there are left alone, like aws s3 sync without --delete (the drive has no delete verb).

A file whose destination copy already matches is skipped, in both directions. The comparison is a content checksum rather than size-and-timestamp: the drive's updatedAt records when an upload landed, not when the source was edited, so it cannot tell you whether the remote is a copy of the file on your disk. Re-running a sync therefore transfers only what actually changed.

Transfers run 8 at a time (--concurrency to change it) and retry a network failure, a 429 or a 5xx with backoff. A file that still fails is reported and the rest of the transfer continues; the exit code is non-zero if anything was left behind. --dry-run prints the plan and moves nothing.

Remote paths use / on every platform. A trailing / forces the directory reading of a remote path — object storage lets notes.txt and notes.txt/child.md both exist, and without the slash a bare path is read as the file when one is there.

pensieve rpc <method> [json-params] [--agent <id>]

Call an RPC method as the connected agent. Pass --agent only when several agents are connected to the same server.

Configuration

.pensieverc.json, the nearest one walking up from the current directory, is the CLI's only configuration input — there are no environment variables.

{
  "server": "http://my-worktree.localhost:5110",
  "home": "./.pensieve"
}

Finding one makes its directory self-contained: a key it omits is answered locally, never by falling through to an outer directory. Omit home and the store is <rc dir>/.pensieve; omit server and it is the hosted default. With no rc file anywhere up the tree, the store is the nearest .pensieve/ walking up, else ~/.pensieve.

pensieve setup writes it; --server is the only thing that puts a value in it.

mkdir test-project && cd test-project
pensieve setup --server http://my-worktree.localhost:5110

server must be the origin the server actually answers on. A value that redirects (the apex instead of www, localhost instead of <worktree>.localhost) files credentials under an origin nothing can find them at.

Reading the SDK

sdk sync and status both print the entry file's path. <store>/sdk/index.ts is the manifest — one export per service, naming every tool and the file it lives in. Read <store>/sdk/services/<name>.ts for the one you are about to call. (The exports say ./services/x.js; the file on disk is ./services/x.ts.)

A service file declares a namespace: DogApi.Client has the methods, DogApi.Breed and friends are the types. The doc comments come from whoever registered the tool and say which arguments matter.

Pensieve's own services

Tables and Files sit in the SDK next to the workspace's tools, exported and called the same way, and are present whether or not any third-party tool is connected.

Tables

A result someone wants to look at belongs in a table, not a local .html/.csv/.md file. Declare the columns once, insert rows, and the human browses it under Tables.

import { Connector, Tables } from "@pensieve/sdk";

const tables = Connector.create(Tables);

const { table } = await tables.createTable({
  name: "Daily todo from Gmail",
  fields: [
    { key: "from", label: "From", type: "text", required: true },
    { key: "task", label: "What to do", type: "text", required: true },
    { key: "deadline", label: "Due", type: "datetime" },
    { key: "priority", label: "Priority", type: "select", options: ["low", "normal", "urgent"] },
  ],
});

await tables.insertRecords({ tableId: table.id, records: [...] });

const { records } = await tables.queryRecords({
  tableId: table.id,
  where: { and: [
    { field: "deadline", op: "lte", value: "2026-08-26T00:00:00Z" },
    { or: [{ field: "priority", op: "eq", value: "urgent" }] },
  ] },
  orderBy: { field: "deadline", direction: "asc" },
});

services/tables.ts carries the field types, the filter ops each one takes, and what every method returns. Three things to know before designing a schema:

  • datetime takes any ISO-8601 instant with an offset — one without is refused rather than guessed at — and comes back as UTC.
  • Ordering by a select follows the order you declared the options in, so declare them worst-to-best and direction: "desc" means "most urgent first" with no extra column. A value outside options is rejected with the list that would have worked.
  • Set label on every field. Without one the grid shows the raw key, so a human reads requested_by instead of Requested by.

A table has no URL of its own — the workspace UI lists them — so tell the human the table's name rather than inventing a link out of an origin and an id.

Files

import { Connector, Files } from "@pensieve/sdk";

const files = Connector.create(Files);

await files.write({ path: "reports/august.md", content: "# August\n…" });
const { entries } = await files.list({ path: "reports" });
const { content } = await files.read({ path: "reports/august.md" });

Paths are workspace-relative — no bucket to name, no tenant prefix. Textual content reads back as utf8 and everything else as base64; past the inline cap read refuses and download returns a short-lived signed URL instead.

Every listing entry and every file info carries a checksum — the hex MD5 of the bytes — so a caller can tell an unchanged file from a re-uploaded one without transferring it.

Past the inline cap on the way IN, uploadUrl returns a short-lived signed PUT and a versionId; the bytes go straight to storage and commitUpload publishes them. Until it is committed the upload is a revision nobody reads, never a half-written file.

A file is the right home for bytes — an attachment, an export someone downloads. A table is the right home for a result someone reads.

Calling a method without an SDK

To check whether a tool answers at all, or to inspect what the server would generate:

pensieve rpc agent.sdk.specs        # the raw broker specs, tool ids included
pensieve rpc agent.sdk.tool.invoke '{"toolId":"…","method":"listBreeds","params":{"limit":3}}'

Every tool call is that one method — the SDK generates exactly these envelopes. The exit code is non-zero on a JSON-RPC error, so this is safe in a script.

When something fails

| What you see | What it means | What to do | |---|---|---| | <agentId> is not connected to <url>. Run: pensieve connect | The store has no credential for that agent | pensieve status lists the ones it does have | | Not connected to <url>. Run: pensieve setup | No credential for this server at all | Run pensieve setup and click Allow in the browser | | Several agents are connected — pass --agent <id> | More than one credential | Decide which agent this task acts as; pass --agent | | Cannot reach <url> | Server down, or wrong url | pensieve status; if it's local dev, is the dev server up? | | -32001 Tool is not connected | The service was never authorized, or was deleted from the workspace | Re-run pensieve sdk sync; if it persists, connect it in the dashboard | | -32000 Token is missing the required scope | The stored token predates a scope | Re-run pensieve connect | | -32602 Invalid params | The arguments don't match the method's schema | Re-read the method's params in services/<name>.ts | | service.name "x" is claimed by both … | Two tools declare the same service name | Rename one in the dashboard |

Never edit anything under <store>/sdk/ — it is regenerated wholesale on the next sync. Never commit .pensieve/; it holds a live bearer token.

Requirements

Node 22 or later.