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

unitbob

v0.8.44

Published

UnitBob connector — thin local hands for the UnitBob Rails brain. Owns no domain logic: it runs tools, relays bytes over the wire, and prints what the server returns.

Readme

UnitBob

UnitBob draws a map of what your app does for people — signing in, paying, sending messages — and puts a lamp on each part. Behind every lamp is a contract, checked automatically, about what that part does. Green means that part still works. Red means something you changed broke it.

You do not write the contracts. Your coding assistant — Claude Code or Codex — does, and UnitBob keeps them honest. Your code stays on your computer; only the contracts and their results go to UnitBob.

Works with apps written in Ruby on Rails, JavaScript/TypeScript and Python. You need Node 18 or newer and Python 3.10 or newer installed.


Install (once)

Claude Code — tell your assistant:

Add the UnitBob plugin marketplace: https://www.unitbob.com/marketplace.json
Install the unitbob plugin

Or, if you prefer the terminal:

claude plugin marketplace add https://www.unitbob.com/marketplace.json
claude plugin install unitbob@unitbob

Codex, in the terminal:

npx -y [email protected] codex-install

Codex runs commands in a sandbox that cannot reach the network or write outside your project. Add this to ~/.codex/config.toml once, so the connector reaches UnitBob and the shared runner store without leaving it (codex-install prints the same lines). If the file already has [sandbox_workspace_write], put the two lines into that table instead: a second copy of the header breaks the file.

[sandbox_workspace_write]
network_access = true
writable_roots = ["~/.npm", "~/.cache/unitbob"]

Then start a new chat. A chat opened before the install does not know about UnitBob yet. Everything below works the same in Claude Code and Codex.

On Codex, the assistant may ask once whether it can continue without a spending limit for its helpers. Answer yes; Codex cannot enforce that limit yet.

Update. Claude Code does not update this plugin by itself. To get a new version, run claude plugin update unitbob@unitbob, then start a new chat. On Codex, run the same npx line with the new version number.

Install from the Claude Code app

No terminal needed:

  1. Open Settings → in the left menu under Customize, click Plugins.
  2. Top-right, click Add ▾ → Add marketplace, enter https://www.unitbob.com/marketplace.json and confirm. Adding a marketplace installs nothing yet; it only tells Claude where the plugin comes from.
  3. Click Browse, find unitbob, and click Install.

The plugin also adds /unitbob:... commands, but they load only when a Claude Code terminal session starts: in the chat where you installed the plugin, and in a browser or desktop window, typing one answers "isn't a recognized command here". Nothing is broken; just ask in words, as below.

The first time you ask for a map, the assistant links your project to UnitBob by itself — no server address, account or id to type. Don't type /init: that is a built-in Claude Code command and has nothing to do with UnitBob. A few approval prompts are normal: building the map and the contracts runs small commands on your machine, and the first map may ask to install graphify. Say yes.


Protect what your app already does

Just say it in the chat. Nothing to memorise, no command to get right.

| Step | Say this | |------|----------| | 1. Draw the map | Build my UnitBob map | | 2. Write the contracts | Generate the UnitBob contracts | | 3. Look at the map | Open my UnitBob map | | 4. Fix a red lamp | Fix the UnitBob contract <id> — the id is written on the lamp |

Step 2 also runs the contracts, so the lamps light up right away. Later, whenever you want to know whether your latest changes broke anything, say Run the contracts.

Your project connects itself to UnitBob the first time. There is nothing to set up by hand.

Buttons on the page

Every phrase on the UnitBob pages is a chip. While a Claude Code session with this plugin is open in your project — in the desktop app or the terminal — the chips become green buttons: Fix it on a red lamp, Fix All for every red lamp at once, Send it to your Agent for the rest. Press one and the phrase reaches that chat, and Claude starts on it as if you had typed it — no copying, no switching windows. The buttons need an up-to-date Claude Code. Without an open session the same chip reads Copy, and you paste the phrase into your assistant yourself, as before. If a press does not get through — the session closed a moment ago — the page says so and offers the phrase to copy instead. The one chip that always copies is I want to add …: it waits for your own words.


Watch your external services

The contracts above keep the outside world out on purpose: payments, email, file storage and AI calls are stubbed, so a red lamp always means your code. That also means they cannot see a vendor that changed its answer, or a key that was revoked. This part can.

| Step | Say this | |------|----------| | 1. Set it up | Watch my external services | | 2. Check again later | Watch my external services |

The External services page shows each service on a board — Broken, Not checked, and the step it has reached — with a button on every card that has something to do. The button's word says what it does for that card; one phrase is behind several words. The buttons send these phrases; you can type them too:

| Button | Phrase | |--------|--------| | Check again, Check after adding the key, Try again, Check after topping up, Check it now | Check the UnitBob external service <id> | | Set it up, Set it up again, Allow test writes | Set up the UnitBob external service <id> | | Allow paid calls, Confirm a test key, Name a test account, Check the real job, Check deeper | Take the UnitBob external service <id> to the next step | | Fix my app | Fix my app for the new answer of the UnitBob external service <id> | | Find out why | Find out why the UnitBob external service <id> stopped | | Fix all (Broken) | Fix all the broken UnitBob external services | | Check them all (Not checked) | Check all the UnitBob external services not checked yet | | Take them all to the next step (Connected) | Take the connected UnitBob external services to the next step |

Checking or rebuilding one service leaves the others as they are: their results, their step, their files.

Every service your map found — Stripe, SendGrid, S3, an LLM — is checked in four steps, and the first check needs nothing from you:

  1. Pulse — the request your app itself sends, sent once with the key it already has; it reads and changes nothing.
  2. Reads through the app — your app makes a reading call the way it does for a user.
  3. Writes to the sandbox — a real action in the vendor's test account, once you said the key opens one.
  4. Writes to a target you named — for a vendor without a test mode: a bucket, an address, a number you can spare.

After the first run the assistant says, in one message, which step each service is on and what the next one takes; answer, or say "later". It never creates anything at a vendor unless you said the account is a sandbox. Before a write, where UnitBob knows the vendor's sign — Stripe by the key, PayPal by the address, S3 by the bucket — it checks that the app is pointed at the sandbox you named and stops grey if it cannot tell; for the rest, your answer is the only guard, and the ladder says so. To change an answer, ask it to set the external services up again.

What it does: it runs your app the way the contracts do, but lets it make its real calls, and each service gets two lamps on the External services page. Access: does the vendor let your app in — the pulse. Work: can the account do the job — money left, quota, the right to write. A key that gets in on an empty account lights Access green and Work red. When nothing could ask about the job for free, Work stays unchecked and says why: even the simplest call that would show it costs money, and the next step is the one that pays. A broken service means the vendor or the key, not your code, so it is never mixed into Run the contracts.

Where the keys come from: wherever your app reads them — a .env file, your credentials, the variables in your terminal. Your keys stay on this machine: UnitBob takes nothing off it. Only your own coding agent, running here, works with them. It never asks you for a key; it may read them to check the connections, but it never writes a key into the contracts or sends one anywhere, and every report is scrubbed of key values before it leaves.

What leaves your computer: the report of the run — scenario names, whether each passed, and the first lines of any error. The process that runs your app scrubs its own key values from that report before the file is written: every environment variable whose name holds KEY, SECRET, TOKEN, PASSWORD or CREDENTIAL and whose value is longer than eight characters is replaced by [redacted]. The connector and the server never see the keys. Keys that are not environment variables are protected by a rule its scenarios follow: error messages print status codes and field names, never response bodies or URLs.

UnitBob runs nothing on a schedule. To check your services every morning, run it from your CI — for example, a GitHub Actions workflow in .github/workflows/unitbob-external-services.yml:

name: External services
on:
  schedule:
    - cron: "0 6 * * *"
  workflow_dispatch:
jobs:
  check:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      # Set up your app's language and dependencies here, as your test job does.
      - run: printf '%s' "$UNITBOB_JSON" > .unitbob.json
        env:
          UNITBOB_JSON: ${{ secrets.UNITBOB_JSON }}
      - run: npx -y [email protected] run integrations
        env:
          STRIPE_SECRET_KEY: ${{ secrets.STRIPE_TEST_SECRET_KEY }}

UNITBOB_JSON is a repository secret holding the contents of your .unitbob.json: it tells the job which UnitBob project to report to, and its token is the only proof the project is yours, so keep it out of the repository itself.


Build something new

The steps above protect what your app does today. When you want to add something new, UnitBob can protect the new part too — before it exists. Then "done" is not a feeling; it is the contracts turning green.

Do steps 1 and 2 above first. Then you say three things in the chat:

| You say | What happens | |---------|--------------| | I want to add <feature> | UnitBob writes down what you want and which of your app's contracts it may touch, and you get a link to the feature's page. If some contracts are red already, you hear it now. Then the assistant reads the code and shows you what "done" means: the list of things that will be true, word for word, and the few places where it guessed — each with options and a recommendation. | | yes — or correct any point | That list becomes the feature's contracts. From here there are no questions: they are written (red, on purpose — the feature is not built yet; one that already holds today stays green), the assistant builds the feature, runs every contract, fixes what the build broke and tells you each time, and a second assistant reads the contracts over. It stops only if something needs you. | | yes to "Finish it?" | The feature is finished: its contracts join your app's and get their own lamp on the map, already green. Only you decide it, and only when everything is green. Pressing Finish this feature on the page, or saying Finish the UnitBob feature <feature>, does the same. |

Say "later" or "stop" at any point and the assistant stops where it is. To pick up again — in this chat or a new one — say the phrase the feature's page shows for where it stands: Let's talk through <feature>, Write the contracts for <feature>, Build <feature>, I'm done with <feature> or Finish the UnitBob feature <feature>.

Good to know:

  • If the button is grey, the page says why — a contract of the feature still fails, the contracts have not been read over yet, or something you said must keep working is red.
  • What the build broke is fixed and named. A contract you said must keep working that breaks while the feature is built is fixed in the feature's code, and the chat says so in a line. One you said would change is updated as planned (its row on the feature page is amber, with the phrase My app changed on purpose — update the UnitBob contract …, which does the same). One nobody saw coming gets a single question: update the contract, or put the code back. Each catch is listed under Caught on the way on the feature's page.
  • The contracts say exactly what you agreed to. Nobody can quietly bend them to fit the code. If you change your mind about the feature, say Let's talk through <feature> again and the contracts are rewritten.
  • Work in your project's main folder. UnitBob keeps the contracts in a folder called .unitbob next to your code. A copy of the project in another folder does not have it.
  • After Finish, nothing more to do. From now on the feature's contracts run with all the others.

If the assistant says it cannot find the UnitBob instructions

The chat was started before the plugin was installed. Start a new chat. If you would rather not, tell the assistant:

Read ~/.claude/plugins/cache/unitbob/unitbob/<version>/skills/unitbob/SKILL.md
and follow the workflow it names for this job.

<version> is the number that claude plugin list shows.


If your app only runs inside Docker

Some projects keep the code on your computer but run it inside a Docker container. Tell UnitBob the container's name in the file .unitbob.json in your project folder:

{ "server": "…", "repo_id": 3, "token": "…",
  "exec": { "docker": { "container": "myapp-web-1" } } }

Keep the other three values as they are; UnitBob wrote them. Now the contracts run inside the container. Without that line everything runs on your computer, as before.

This works only when the project folder is shared with the container — which it already is if editing a file on your computer changes the running app. If it is not, UnitBob tells you and stops.

A few things UnitBob does not do in a container yet:

  • The container has to be running already; UnitBob does not start it.
  • On Linux, files written from inside the container belong to root.
  • If a contract takes too long and is stopped, the container may keep running it in the background for a while.