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

@outerlayer/cli

v0.1.1

Published

OuterLayer CLI: init/doctor (hooks + daemon onboarding), watch, scan, and eval (validate/mine/qualify/report — execution-verified evals from your own PRs)

Readme

outerlayer

Capture what your coding agents actually did, and sync it to your OuterLayer cloud workspace.

OuterLayer captures the sessions your coding agents already write to disk (Claude Code, Codex CLI, Cursor) and syncs them to your OuterLayer cloud workspace, where you and your team can see what your agents did across every repo. Capture itself is local: it reads the session files your agents already write to disk. Uploading is a separate, launch-gated step — what reaches the network, and when, is below.

Launch a session naming the work it's for, then sync:

npx @outerlayer/cli init                          # install the capture hooks
npx @outerlayer/cli work add --issue 42           # create the work item; prints its number
OUTERLAYER_WORK=7 claude                     # launch a session naming that number
npx @outerlayer/cli sync --dry-run                # see exactly what would leave your machine
npx @outerlayer/cli sync                          # upload sessions launched that way

Privacy, stated plainly

A session uploads only when you launch it with OUTERLAYER_WORK naming the work item it's for. Create the item first with outerlayer work add, which prints its factory-scoped number, then set that number before starting your agent — OUTERLAYER_WORK=7 claude. The session-start hook then records the launch and the session uploads whole, from its first turn, until it ends. A session launched without the variable never leaves the machine, in any tool, however sync is invoked. There is no way to turn upload on mid-session — the decision is made once, at launch.

Only Claude Code has a session-start hook today, so only Claude Code sessions can be launched as work. Codex CLI and Cursor sessions are captured locally and never upload.

outerlayer sync is the complete upload, and it sends only launched sessions. It runs when you run it. The hooks also fire outerlayer sync --quiet in the background after each agent turn and when a session ends, at most once every five minutes, as soon as outerlayer login has saved credentials. So a launched session uploads while it is still running, not only once it is over. Set "autoSync": false in ~/.outerlayer/config.json to leave every automatic upload — the background sync and the daemon's streaming below — to your own command.

outerlayer daemon uploads a launched session as it grows, turn by turn, while it runs, and nothing else. The same launch gate as sync applies: a session started without OUTERLAYER_WORK is never streamed, in whole or in part, and neither is a session the repo filter excludes. It sends only the turns since the last one it already sent, and it stops sending the moment the session ends. "autoSync": false stops it too, and it is read on every send, so turning it off takes effect without restarting the daemon. outerlayer sync still runs on top of this and remains the complete, authoritative upload — the daemon exists so a session's page can follow along before that sync ever runs.

These commands also talk to your workspace. None of them sends session content:

  • outerlayer work add, remove, status, list, link-session, pr, claim, renew, release read and write your Floor. The session-start hook spawns outerlayer work link-session when you launch a session with OUTERLAYER_WORK, naming the item and the session id — the item itself must already exist, created ahead of time with outerlayer work add. outerlayer work pr sends the session id and the pull request number and repository — never the session's own content. outerlayer work claim records a lease for this host before it starts working on an item, so two hosts never build the same piece of work at once; renew extends it and release marks it done. A runner key needs git_connection.read to list and read items, work_item_claim.insert to claim, and work_item_claim.update to renew or release — the two claim permissions are not granted to a dashboard role by default, since a claim is a host's lease, not a person's.
  • outerlayer runner start lists, claims, renews and releases work items the same way work claim/renew/release do, on the schedule its config sets — it sends nothing about the job it runs beyond that; the agent it starts is a separate process with its own credentials, from the runner block, never the copy-out daemon's. runner check makes one list call to confirm the key works and sends nothing else; runner init, runner status and runner logs make no network call at all.
  • outerlayer emit artifact uploads the file you name — a screenshot, a recording, a report, a log — along with its caption. With no recorded session to attach it to, it uploads immediately, anchored to a pull request or to the git checkout.
  • outerlayer emit <name> and outerlayer emit commit-credit send one check's outcome, and one commit's attribution, for a work item. Run from inside a session, they send only when that session carries a launch record — the session's own content may not leave by a second route. Run from CI or a plain shell, where there is no session, they send as they always have.
  • outerlayer emit finding and outerlayer emit findings <file> send one finding, or a whole batch of them, for a work item — the same anchoring as emit <name> (--item, or the recorded session's own item), except a session may record findings on the item it was launched for.
  • outerlayer mcp serve is the stdio MCP server your editor spawns. It forwards every JSON-RPC message the editor sends to the gateway and returns the reply.

The session-start hook reaches the network in two more cases:

  • In a repository your factory governs, it asks the control plane which context repository and ref this checkout is pinned to (GET /v1/context/source, with your API key), then git fetches that ref. It sends the repository name; it degrades to the last known commit when the control plane cannot be reached.
  • When your repository's git hooks are missing, it runs that repository's own prepare script (yarn prepare / npm run prepare). What that script does is your repository's business; installers commonly download packages.

The daemon's one-shot sweep (outerlayer daemon --once) makes no network calls — the copy-out mirror it performs is the same one the long-running daemon runs continuously, and neither the sweep nor an ineligible session in the long-running daemon opens a connection. The session-start hook makes none either, in a repository your factory does not govern. All three are checked rather than promised: a test runs each of them with fetch and net.Socket.prototype.connect replaced by stubs that throw, and asserts nothing tried to connect. Those two stubs cover every outbound path this process opens itself — anything built on node:http, node:https or node:tls opens its socket through connect, and fetch opens its own below that method. A child process opens its sockets in its own address space, past both stubs, so the same test records every program each run starts and asserts none of them fetches over the network.

Failures the hook cannot show you — a refused OUTERLAYER_WORK value, a work add that could not reach the Floor — are appended to ~/.outerlayer/spool/hook-errors.log, and the next session start says so. The addition retries a gateway it cannot reach a few times, with backoff, before it gives up.

  • The tier is applied before anything leaves. The default tier is full: message text, thinking, images, and tool input/output all ship. --tier redacted strips that content client-side, keeping only structure (repos, branches, file paths, tool names, error signatures) and metrics; --tier metrics strips identifiers too (the server additionally clamps to your org's configured ceiling — sending more than it allows stores less).
  • --dry-run shows exactly what would leave — per-session rows, image bytes, and the precise field classes stripped at the chosen tier. Add --json to inspect the literal request payloads. Zero network calls.

Commands

| Command | What it does | |---|---| | outerlayer sync | Upload sessions launched with OUTERLAYER_WORK to your OuterLayer cloud workspace (incremental — only what's new since the last sync). Tier-gated client-side (--tier metrics\|redacted\|full, default full); --dry-run prints exactly what would leave the machine; --all re-sends everything (idempotent server-side). Credentials come from outerlayer login, OUTERLAYER_* env vars, or --url/--app-id. | | outerlayer login [--url] [--app-id] | Save the cloud URL, app id, and API key to ~/.outerlayer/config.json once. The key is read from stdin (echo "$KEY" \| outerlayer login …) or a prompt with echo off; it is never a flag. --no-input refuses to prompt. | | outerlayer init | Install the capture hooks and the status-line segment. --json for scripts. Run through npx, it copies the CLI to ~/.outerlayer/cli and points the hooks at that copy, so they survive npm clearing its cache; npx @outerlayer/cli@latest init upgrades it. From any other install, the hooks run that install. Claude Code deletes transcripts after ~30 days; run outerlayer daemon separately to mirror them first — init does not start it for you. | | outerlayer daemon | Run the copy-out daemon in the foreground (--once for a single sweep, which uploads nothing). Once cloud credentials exist, it also streams a launched session's new turns as they land — see Privacy, above. outerlayer watch is the former name and still works, with a warning. | | outerlayer doctor | Check the installation: hooks, status-line freshness, and sync health. --json prints the checks and a summary for scripts. | | outerlayer context emit [--check] | Compile .outerlayer/ into each configured target tool's native files (targets come from .outerlayer/config.json). --check computes outputs and diffs against disk without writing (CI mode). Bare outerlayer emit with no name still compiles, with a deprecation warning. | | outerlayer import ruler | Port a .ruler/ tree (Ruler) into the equivalent .outerlayer/ tree — mostly a rename; never overwrites an existing .outerlayer/. | | outerlayer hooks wrap / outerlayer hooks unwrap | Auto-wrap (or undo wrapping) PreToolUse/PostToolUse hooks for execution evidence — one spawn per firing. | | outerlayer emit artifact <file> --caption <text> [--for <criterion-id>] [--pr <n>] | Upload a proof artifact — screenshot, recording, report, or log — with its caption. Inside a recorded session it spools locally and ships on the next sync; otherwise it uploads immediately, anchored to a pull request or the current checkout. --replaces retires artifacts an earlier run uploaded. | | outerlayer emit <name> --result <pass\|fail> [--link <url>] [--body <text>\|--body-file <path>] --item <number> | Record one named check's outcome on a work item. A check that ran carries the run URL as its proof (--link); a judgment you are making yourself carries one sentence (--body, or --body-file to read it from a file — - reads standard input). A fail needs at least one of the two; a pass needs neither. --item names the work item by the number printed when the item was created — always required, in or out of a recorded session; from inside a session it can only name an item that session was NOT launched for (a session cannot record a check on its own item). Prints the recorded check's id. Who recorded it comes from your API key, never from what you send. | | outerlayer emit artifact-review --result <pass\|fail> --artifact <id> [--body <text>\|--body-file <path>] | Record a person's own pass or fail on one artifact — evidence already emitted, bound to a criterion. --artifact names it and replaces --item; the gateway resolves the work item from the artifact's own pull request. --link is refused. A fail reads its sentence from --body/--body-file, or from standard input when neither is given. Refused from inside a recorded session — an artifact verdict is a person's act, the same rule the gateway enforces. Prints the recorded verdict's id. | | outerlayer emit commit-credit --pr <n> … | Send one commit's attribution for a pull request. | | outerlayer emit finding --id <id> --subject <change\|context> --title <text> --file <path> --kind <behavior\|hygiene\|proof> --verdict <confirmed\|refuted\|unverified> --source <implementer\|reviewer\|refuter\|gate> --where <label> [--item <number>] … | Record one finding — what was found wrong about the change (--subject change) or about a rule it ran on (--subject context, which then needs --rule-path, --rule-quote and --rule-relation together — all three are required, not just --rule-path). Validated against the same contract the gateway checks before anything is sent. --item names the work item; without it, inside a recorded session, the item that session was launched for is used automatically — unlike emit <name>, a session may record findings on its own item. Re-emitting the same --id on the item replaces that finding. | | outerlayer emit findings <file> [--item <number>] | Record a whole batch at once, read from a FindingBatch JSON file (- for standard input) — the same shape and validation as emit finding, one record per subject/title/file/kind/verdict/source/where. Anchored the same way: --item, else a recorded session's own item. | | outerlayer mcp install [--transport stdio\|http] [--url] [--name] [--command] | Write (or update) an mcpServers entry in .mcp.json for the OuterLayer gateway. Default stdio: the client spawns outerlayer mcp serve, which reads the API key from ~/.outerlayer/config.json (or OUTERLAYER_API_KEY) each time it connects, so a reconnect picks up a newly saved or rotated key. --transport http writes a direct POST /v1/mcp entry referencing ${OUTERLAYER_API_KEY}, resolved by the client from the environment it was launched with. Never writes an API key. Pass --url/--app-id for self-host. | | outerlayer mcp serve [--url] [--app-id] | Stdio MCP server bridging stdin/stdout JSON-RPC to the gateway's POST /v1/mcp. What the stdio .mcp.json entry runs; exits 1 with a clear message when no API key is configured. | | outerlayer work add --issue <n>\|--pr <n> [--repo] [--note] | Records that a source — a recorded session, a CI run, or the API key's bound member — is working on an issue or pull request. The only way an item becomes visible on the Floor. | | outerlayer work remove --issue <n>\|--pr <n> --reason <text> [--repo] | Withdraws the caller's own addition, recording the reason. Never deletes anything; the item leaves the Floor only once no live addition remains on it. Removing again is a no-op that still succeeds. | | outerlayer work status --issue <n>\|--pr <n> [--repo] | Shows one item's stage, section, gate ledger, linked pull requests and sessions, and its additions. | | outerlayer work list [--stage] [--section] [--repo] [--unclaimed] [--startable] [--needs amend] | Lists live work items for the current factory, filterable by stage, section, claim state and whether an open fail is waiting for an answer. | | outerlayer work pr <n> [--repo] [--session-id] | Run inside a session on a work item: declares that pull request <n> in the checkout's repository belongs to that item. Idempotent — declaring the same pull request twice is a no-op. Refuses when the session is on no item, or the repository is not connected. | | outerlayer work claim --item <n>\|--issue <n> --kind implement\|amend [--repo] [--host] [--seconds] | Records a lease for this host (default: its own hostname), so two hosts never build the same item at once. A live lease already held by a different host is refused; claiming again under the same host extends it. Lease length defaults to 900 seconds, the server's own cap. | | outerlayer work renew --item <n>\|--issue <n> [--repo] [--host] [--seconds] | Extends this host's own live lease. Refused if the lease has expired, or if a different host holds it. | | outerlayer work release --item <n>\|--issue <n> [--repo] [--host] [--outcome] | Marks this host's lease released, optionally recording how the attempt ended. Releasing an already-released lease is a no-op that returns the recorded release time and outcome. | | outerlayer runner init [--config <path>] | Writes a runner block with defaults and three example hooks, keeping every key the config already had. Refuses rather than overwriting an existing block. | | outerlayer runner check [--config <path>] | Validates the config, the hooks and the key, prints the settings the runner would use, and exits non-zero on a problem. Takes no work and writes no pid file. | | outerlayer runner start [--config <path>] | Runs the loop: claim work, run it, sync, clean up, report, release with an outcome. Repeat. Reads the runner block from the config file (default ~/.outerlayer/config.json). Refuses to start on a bad config, an unrunnable hook, or a refused key. | | outerlayer runner stop [--config <path>] [--now] | Takes no more work and waits for running jobs to finish, naming each every five seconds. --now ends them immediately with outcome stopped. Ctrl-C on a foreground runner drains; a second one stops now. | | outerlayer runner status [--config <path>] [--recent] [--json] | A header naming the runner's own state, then a row per running job — item, queue, stage, elapsed, started, lease, job directory. --recent adds finished jobs, newest first, capped at 20, with their outcomes. | | outerlayer runner logs <item> [--config <path>] [-f] | Prints the newest attempt's log for one item, following it with -f. |

The work commands need an API key that carries Ingest traces (trace.write) to add, remove, and declare a pull request (pr), and Read the Work page (git_connection.read) to look an item up — which remove and status both do before they act. Tick both when you mint the key, in Settings → API keys. Claiming a lease needs Claim work items (work_item_claim.insert); renewing or releasing one needs Renew or release work item claims (work_item_claim.update). Withdrawing an addition somebody else made additionally needs Withdraw others' work from the Work page (git_connection.update).

Recording your own pass or fail on a check

outerlayer emit also records a judgment you make yourself, under a check name a validator declares, on the work item — not any one pull request, so it holds across every pull request the item has. Say what is wrong, in one sentence:

outerlayer emit code-review --result fail --item 412 \
  --body "The button should be the destructive red, not grey."

The sentence can come from a file, or from standard input with -:

echo "The button should be the destructive red, not grey." \
  | outerlayer emit code-review --result fail --item 412 --body-file -

Record a pass once the work is right:

outerlayer emit code-review --result pass --item 412

--item is always required, in or out of a recorded session. A session records a check only on an item it was NOT launched for, by naming it with --item; a check on the item the session itself was launched for needs a machine key (an API key run outside the recorded session) or CI.

Three things to know. Who recorded a check is decided by the API key you used, never by anything in the request. A key bound to a membership records the check against that person, and once a fail is recorded, only a person's key can record over it. A failing check needs a run link or a sentence, because a fail with neither is something nobody can act on. And a check only holds a gate once a validator in .outerlayer/validators/ declares its emit name; without one the check is recorded and read back, and blocks nothing.

A review of the work itself is different: it is recorded from the Review tab on the work item page, not the CLI, and holds every pull request of the item until a person records a pass — no validator declaration needed. outerlayer emit refuses work-review outright; recording it anywhere but the work item page is not supported. The gateway refuses a review, and an artifact verdict, on an item that is closed or shipped, has no evaluation yet, or whose evaluation is still waiting on a session link, with the code item_not_reviewable. A recorded pass carries the head commit of every pull request the item had at the time; once one of them moves, that pass stops counting and a fresh review is needed. A repository can require a current pass before the evidence check completes by setting the base branch's review policy key to required (default optional, alongside merge_gate) — the check then stays in progress, not failed, until a current pass exists.

outerlayer emit artifact-review sits between the two: a person's own pass or fail, like a review, but on one piece of evidence rather than the whole item. A fail here holds the item the same way a review's fail does, and counts the work as bad on Quality once, however many artifacts carry one — it also lists the item on a queue the API exposes, naming the artifact and why it failed, so a host with a session linked to the item can answer it with a replacement, or the fail's own recorder can pass over it directly. The item's own pass stays available only once every required artifact has a pass and no fail is still open.

Every command accepts --no-color, and color is off on its own when stdout is not a terminal, when NO_COLOR is set, or when TERM is dumb. FORCE_COLOR=1 turns it on for a pipe. Commands that print anything accept --json.

Status line

init also adds an ambient Claude Code status-line segment showing what your session and your agents are costing today:

⬢ OL  $0.87 session · $23.40 today across 3 agents · 12 unsynced

The session figure comes straight from Claude Code's own cost field, so it always matches what Claude Code itself would show. The cross-agent total and unsynced count come from ~/.outerlayer/statusline.json, a small file the watch daemon keeps fresh — the status line itself never parses transcripts, so it stays well under Claude Code's refresh budget. On a day with only one active agent, the scope adapts: across N agents becomes across N sessions if you ran several sessions with it, or drops entirely to a bare $X today for a single session — "across 1 agent" never appears.

If a statusLine command is already configured, init wraps it rather than replacing it: your existing command's output is printed first, the OuterLayer segment appends after. A hang or failure in the wrapped command never blanks the line — it times out and OuterLayer's segment prints alone. outerlayer init --remove restores the original command exactly.

Without outerlayer watch running, the line degrades gracefully to the session figure alone plus a dim outerlayer doctor hint — run outerlayer doctor to see why (usually: the daemon isn't running, or hasn't refreshed recently).

Opt out of the segment with outerlayer init --no-statusline.

Supported agents

| Agent | Source | Status | |---|---|---| | Claude Code | ~/.claude/projects (+ raw mirror) | full: turns, tool I/O, thinking, images, subagents, cost | | Codex CLI | ~/.codex/sessions | captured locally only — turns, tool I/O, edits (apply_patch), errors, usage | | Cursor | ~/.cursor/chats | captured locally only — turns, thinking, tool I/O, edits, errors, no cost (Cursor stores no token usage) |

Upload needs a session-start hook to record the launch, and only Claude Code has one, so only Claude Code sessions can be launched as work today.

Sessions from every agent land in one canonical schema (@outerlayer/session-schema), so sync and everything downstream treat them identically. Adding an agent is one source adapter.

How capture works

Your agents already write complete transcripts to disk — OuterLayer treats those as the source of truth rather than wrapping or proxying the agent:

  • init adds a <50ms hook that notes each session event and installs the status-line segment.
  • outerlayer daemon mirrors transcripts before the agent deletes them — so history survives even for agents with retention windows. It's a separate, long-running process you start yourself; init doesn't start it for you.
  • sync parses whatever is on disk and ships the sessions you launched with OUTERLAYER_WORK, incrementally, only what's new since the last run.

No API keys, no model calls, no interception. If you uninstall OuterLayer, your agents never notice.

Requirements

Node 22+. macOS and Linux; Windows untested (issues welcome). Capturing Cursor sessions additionally needs Node 22.5+ (it reads Cursor's SQLite chat store via the node:sqlite builtin); on older Node, Cursor is not captured at all.