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

hitl-cli

v0.5.0

Published

Reach a human from inside an agent session.

Readme

HITL

Reach a human from inside an agent session. Your phone buzzes when the agent finishes, when it gets stuck, and when it needs a decision only you can make — and its question waits for your answer.

npm i -g hitl-cli
hitl login
hitl init

Three lines, per project. Sessions in that project are connected: no scripts to copy, no keys to paste, no settings to hand-edit — and no other project on your machine starts notifying because of it.

Status: early. Everything below works — the server is deployed, the commands run, and the whole path has been exercised end to end against production. The version number is low because it is new, not because it is a sketch: expect rough edges, and expect flags to settle before 1.0.

The one thing you cannot do from a terminal is approve your own machine — that step is a browser and a signed-in human on purpose, and it is what stops a leaked code being worth anything.


Install

npm i -g hitl-cli

Node 18 or newer. Nothing else — no Python, no jq, no shell scripts.

Sign in

hitl login

Prints a short code and opens the HITL admin site in your browser:

  Open  https://hitl-admin-web.fly.dev/device
  Code  BXQT-7K2M

  Waiting for approval…

In the browser you sign in with Google, see which machine and which project is asking, and approve. If an application already matches the project, it is preselected; if none does, you get a one-click Create and approve already named after the folder. The terminal picks the credential up on its next poll.

Logins are per project

hitl login signs in the project you are standing in, not just the laptop. Two repos on one machine can reach two different audiences, and cd is the only thing that decides which:

cd ~/Dev/hitl && hitl login    # sends as HITL
cd ~/Dev/oxy  && hitl login    # sends as Oxy

The project is its git repository root, so running a command from src/components is still the same project as running it from the top.

Credentials live in ~/.hitl/config.json (mode 0600), keyed by project path — deliberately not in a file inside the project, because a credential in a working tree is one that ends up in a commit or a container image.

Resolution, most specific first: HITL_TOKEN from the environment, then this project's credential, then a machine-wide one from hitl login --machine.

Three things worth knowing:

  • The application decides who gets buzzed. A request goes to that application's members. If you are the only member, it goes to your phone. Adding teammates to the application is done on the same site.
  • The credential belongs to this machine. Revoke a laptop without touching any other machine, from Agents in the admin site.
  • The code is safe to read aloud. It is useless without someone signed in approving it, and it expires in 15 minutes. The secret half never leaves the terminal.

Check it worked:

hitl status

Turn the hooks on

hitl init

This writes HITL's hooks into this project's .claude/settings.json, merging into whatever is already there rather than replacing it. Sessions in this project are connected; nothing else on the machine changes.

hitl init --global      # every project on this machine instead
hitl init --print       # show the JSON and change nothing
hitl init --remove      # take them back out (add --global for those)

hitl init refuses to run in a project that has no credential of its own, rather than quietly adopting one from elsewhere and sending this project's notifications as some other application.

A project only notifies if you ran hitl login in it. That holds even with --global: the hooks fire in every session, but one with no credential of its own does nothing. So --global is safe — it means "be ready everywhere", not "report everything to whichever project I signed into last".

hitl init is idempotent. Run it again after an upgrade and it reconciles.

What it writes

No hidden magic — this is the whole of it:

{
  "hooks": {
    "UserPromptSubmit": [
      { "hooks": [{ "type": "command", "command": "hitl hook", "args": [], "timeout": 10 }] }
    ],
    "Stop": [
      { "hooks": [{ "type": "command", "command": "hitl hook", "args": [], "timeout": 15 }] }
    ],
    "Notification": [
      { "matcher": "permission_prompt|idle_prompt|agent_needs_input",
        "hooks": [{ "type": "command", "command": "hitl hook", "args": [], "timeout": 15 }] }
    ]
  }
}

hitl hook reads the event on stdin and decides what to do with it. That is why there is no script to install: the CLI is the hook handler.

What you get

| When | What arrives | |---|---| | The agent finishes a turn | Onyx is done in hitl — with what it actually did, not just that it stopped | | It hits a permission prompt | Onyx needs you in hitl — including the command it wants to run | | It has been idle waiting on you | a nudge, once | | It asks you something | a question you answer from the lock screen; the session waits |

Each card says which project and which agent it came from, so two sessions in two repos are never confused for one another.

Ask a human, from inside a session

The hooks are one direction. This is the other:

hitl ask "Ship v2.41 to production?"

It blocks until you answer on your phone, prints your answer to stdout, and exits non-zero if you refuse or it times out. That is the whole interface, and it is the point of the product: an agent can stop and genuinely wait for you.

Tell your agent it exists — a line in CLAUDE.md is enough:

When you need a decision only I can make, run hitl ask "<question>". It blocks until I answer on my phone and prints my answer. Prefer it over stopping and waiting. Don't use it for anything you can work out yourself.

Add Bash(hitl:*) to your allowed tools so it doesn't prompt every time.

Commands

| Command | Does | |---|---| | hitl login | Signs in this project. --machine for a machine-wide credential. | | hitl logout | Revokes this project's credential, server-side too. --machine for that one. | | hitl status | Project, credential, where it came from, hooks, and whether it works. | | hitl doctor | Why a notification did not arrive. --send posts a real one. | | hitl init | Writes the hooks for this project. --global, --print, --remove. | | hitl ask "…" | Asks a question. Blocks. Prints the answer. | | hitl notify "…" | Tells you something. Returns immediately. | | hitl approve "…" | Asks for a yes/no, or --option repeated for a custom set. | | hitl hook | The hook handler. Called by Claude Code, not by you. | | hitl mcp | Runs as an MCP server over stdio, for clients that prefer tools to a CLI. |

Keeping it quiet

A Stop hook fires on every turn. Left alone that is sixty buzzes in an afternoon, and you will mute HITL by Thursday — so hitl init ships a filter already on: a turn is only worth telling you about if it took longer than 90 seconds, because a turn that short means you never walked away.

hitl config set minSeconds 30    # chattier
hitl config set minSeconds 0     # every turn
hitl config set stop off         # done-notifications off, questions still work
hitl config set idle on          # also nudge if you have not looked after a minute

idle is off by default. Claude Code fires it about a minute after a turn ends — which is a minute after the finished notification already told you the same thing, so left on it buzzes twice for one event. Turn it on if you want the reminder.

Everything the CLI sends carries an idempotency key derived from the session and the turn, so a retry after a dropped connection can never buzz twice for one event.

Where things live

| Path | What | |---|---| | ~/.hitl/config.json | Credentials per project, plus settings. Mode 0600 | | .claude/settings.json | This project's hooks, after hitl init | | ~/.claude/settings.json | Every project's hooks, with hitl init --global |

When it doesn't work

Nothing arrives. hitl status first. If that is healthy, the request reached Apple and the problem is on the phone — check Settings → Notifications → HITL for Scheduled Summary, a Focus filter, or Deliver Quietly, which one accidental tap from a notification's context menu will turn on.

Anything at all. Run hitl doctor. It checks, in the order a notification travels: is the CLI current, are the hooks installed, does the path in the settings file still exist, would a hook find a credential here, is anything muted, is the server reachable, is the credential still valid. Add --send to post a real notification and see it arrive.

That last-but-one check is the one worth knowing about: a hook resolves credentials differently from anything you type — it refuses a machine-wide fallback — so hitl status can look healthy while hooks send nothing. Only doctor answers that question.

Hooks don't fire. /hooks inside Claude Code shows what is loaded and where it came from.

A project notifies that you did not expect. It was signed into. hitl logout there, or hitl init --remove to stop that project reporting while keeping the credential.

"command not found: hitl" in the transcript. Claude Code runs hooks in a non-interactive shell that may not have your PATH. hitl init writes an absolute path for exactly this reason; re-run it after moving your Node install.

Too noisy. See Keeping it quiet. Do that before muting the app.

It buzzed twice. Report it — the idempotency key should make that impossible, so it is a bug rather than something to configure around.

Removing it

hitl init --remove     # hooks out of the settings file
hitl logout            # credential revoked server-side
npm rm -g hitl-cli

hitl logout revokes on the server, so a credential cannot outlive the machine that held it. Revoking from the admin site's Agents page does the same thing from the other end.