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

@idlepaydev/claude-code

v0.1.1

Published

Get paid for waiting. Sponsored status line for Claude Code — 50% of ad revenue goes to you.

Readme

@idlepaydev/claude-code

Get paid for waiting. While Claude Code's agent is working, your status line shows one sponsored line. Advertisers bid; 50% of ad revenue goes to you — the developer whose terminal showed the ad. Same backend, same ~/.idlepay account, and same anti-fraud contract as the shipping opencode plugin (@idlepaydev/plugin); a machine signed in from either editor is recognized by the other.

●  Ship faster with Acme CI   ·   $0.42 today · $12.80 total

Integration approach

Claude Code has no plugin runtime that stays resident the way opencode's TUI plugin does. It instead lets you hook two extension points in ~/.claude/settings.json, and it runs each as a short-lived process — spawned on demand, fed JSON on stdin, then it exits. The integration is built entirely from those one-shot processes, coordinating through small JSON files under ~/.idlepay (the same directory the opencode plugin uses):

| Process | Wired via | Role | | --- | --- | --- | | statusline.js | settings.jsonstatusLine | Renders the sponsored line. Also the impression clock tick, the ad/earnings refresher, the receipt flusher, and the /ad-nudge scheduler. | | hook.js <Event> | settings.jsonhooks | Busy detection. Translates a turn-lifecycle event into the busy flag + presence timestamp in runtime.json. | | ad.js | ~/.claude/commands/ad.md (the /ad command) | Click-through. Opens the current ad's URL in the default browser and reports a click. | | commands.js <sub> | ~/.claude/commands/idlepay{signin,signout,refresh}.md | Manual sign-in / sign-out / refresh — parity with opencode's /idlepay* commands. One binary, sub-command via argv. | | cli.js | npx @idlepaydev/claude-code setup\|update\|remove | One-command installer (and update = re-fetch @latest and re-run setup). |

State files under ~/.idlepay:

  • credentials.json — machine token after device sign-in (shared with opencode).
  • runtime.json — busy flag, last human-input time, current ad + signed assignment, pending sign-in code, cached earnings.
  • cc-clock.json — the impression clock's running state, persisted because no process survives between ticks.
  • cc-queue.json — pending impression receipts awaiting flush.
  • health.json — last successful API call time (drives the connection dot).

Render surface — the status line

Configured as:

{
  "statusLine": {
    "type": "command",
    "command": "node /…/@idlepaydev/claude-code/dist/statusline.js",
    "refreshInterval": 1
  }
}

Claude Code runs the command, pipes the session JSON on stdin (session_id, cost, model, …), and prints whatever the command writes to stdout as the status line (docs). We read stdin only to drain it; all integration state comes from runtime.json. The line uses ANSI color (a green/amber connection dot, green "today", blue "total"), matching the opencode TUI palette.

Refresh cadence assumption (this is load-bearing). The status-line command is our only periodic wakeup. Claude Code re-runs it after each assistant message, debounced at 300 ms, and — when refreshInterval is set (minimum 1 second) — also on that fixed timer. We set refreshInterval: 1, so during a busy span ticks arrive ~1 s apart. With IMPRESSION_VIEW_MS = 5_000, one impression needs ~5 consecutive busy + present ticks. The installer sets this for you; without it the clock simply stops accruing during idle gaps (see below), which is safe — it never over-credits.

Busy detection — hooks

Claude Code has no "session busy" event. It does fire hooks around the agent turn (docs). We wire hook.js to seven events and model busy as the span between a turn-starting event and its matching Stop, keyed per session_id:

| Event | Effect | | --- | --- | | UserPromptSubmit | busy = true, and refresh lastInputAt (human present). | | PreToolUse / PostToolUse / SubagentStart | busy = true (agent mid-turn). | | Stop / SubagentStop / SessionEnd | busy = false (turn / session ended). |

Each event's session_id (from the hook payload) keys the busy state, so the agent view's many concurrent sessions never cross-credit each other — see Documented limitations → Agent view. The reducer (src/busy.ts) is pure and unit-tested. The hook process is local-only, never blocks Claude (exits 0, no stdout), and never touches the network. The event name is passed as argv so a single binary serves every event.

Impression accrual

The four gates for a valid impression — busy + focused + adVisible + human presence — are unchanged from the shared gates.ts. On each status-line tick we rehydrate the persisted ImpressionClock, feed it one tick(now, gateState), and re-persist it. Completed impressions are queued as receipts and flushed to POST /api/telemetry/impressions (idempotent on jti + seq, so retrying a flush is always safe). The clock's 2 s "stalled" guard discards any tick whose gap exceeds 2 s, so laptop sleep, a suspended process, or a quiet status line never credit wall-clock time the user didn't actually watch.

The server is the source of truth for all anti-fraud (signed machine-bound assignments, idempotent receipts, rate caps). These client gates are honesty, not security.

Click-through — the /ad slash command

The status line renders the ad's URL as plain text, so most terminals auto-linkify it and an alt/ctrl-click opens the sponsor directly (the same way the sign-in URL is clickable). That click goes through the terminal, not the app, so it drives advertiser traffic but reports no credited click — by design, we never over-credit. The credited path is the /ad command below.

A plain status line captures no mouse clicks of its own, so the billable click path is an explicit command. setup installs a custom slash command at ~/.claude/commands/ad.md (slash-command docs). Running /ad:

  1. reads runtime.json for the current ad (url, brandName) + assignment;
  2. if there's no active ad, prints No active ad right now. and stops;
  3. otherwise opens url in your default browser (cross-platform: start on Windows, open on macOS, xdg-open on Linux);
  4. best-effort reports the click via POST /api/telemetry/clicks and prints a one-line confirmation (Opened <brand> ↗).

A click bills at 6x the impression rate (hard-capped at 1/min and 5/hour per user), and the server additionally requires a prior validated impression for that assignment and accepts one click per assignment, so a click may be rejected — that's expected. Billing is best-effort: the link always opens even when the click is rejected or you're offline. The command file uses Claude Code's inline bash injection (!`node …/ad.js`, with allowed-tools: Bash(node …)) so invoking /ad actually runs the helper and injects its confirmation line; remove deletes the file only if it carries our marker.

Discoverability nudge

Because /ad is invisible until you know it exists, the status line teaches it: roughly once every 20-30 impressions (interval re-rolled randomly in [20, 30] each cycle) it briefly appends · ↗ /ad. The nudge is visible for ~3 seconds, then the line reverts. Since the status-line command is a stateless one-shot, this is driven by two persisted runtime fields (nextNudgeAtSeq, nudgeUntilMs): when the impression clock's seq reaches the threshold we stamp a 3 s window and roll the next threshold seq + randomInt(20, 30). The decision is a pure function (src/nudge.ts) and the renderer only reads whether the window is currently open, so both stay unit-testable.


Documented limitations (read this before live-testing)

Claude Code's extension surface is narrower than opencode's TUI, and this integration is honest about what it cannot observe:

  1. Click-through is command-driven, not click-on-the-line. A plain status line receives no mouse events, so the line itself can't be clicked. The click path is the explicit /ad slash command instead (see Click-through above): it opens the ad and reports a click, billed at 6x (capped 1/min, 5/hr) subject to a prior impression and one-click-per-assignment. The discoverability nudge teaches users the command exists. We do not claim a click the user didn't make — only an explicit /ad invocation reports one. (The status line now OSC 8-hyperlinks the advertiser URL to our /r forwarder, which records a credited click server-side before redirecting — so on terminals that support OSC 8 (iTerm2/Kitty/WezTerm; Windows Terminal may need FORCE_HYPERLINK=1; Terminal.app no) an alt-click is billed. Where OSC 8 is unsupported it degrades to a bare, uncredited link, so /ad stays the reliable always-billable path.)

  2. Weaker focus / human-presence gate. Claude Code exposes no terminal focus signal and no keypress signal to hooks or the status line (unlike the opencode TUI, which gets renderer focus/blur and keypress events). So:

    • focused is always reported true — we cannot detect a blurred terminal. The gate field is retained only for parity with the shared logic.
    • Human presence uses the last UserPromptSubmit as its proxy (tool activity does not count, since that's the agent acting, not the human), bounded by the 15-minute HUMAN_PRESENCE_WINDOW_MS. This is the strongest "a human is here" signal Claude Code gives us, and it is weaker than the opencode keypress signal. It is documented, not hidden.

    The server-side caps and the proof-of-work economics (a busy window burns the user's own API tokens, which cost more than the ad pays) remain the primary anti-farming controls, so the weaker client gate does not change the security posture — only the honesty of client-side display.

  3. Tick cadence depends on refreshInterval. If a user removes refreshInterval from their settings, the status line only re-runs on assistant messages, and the clock will rarely accrue a full 5 s window. This under-counts; it never over-counts.

  4. Agent view (claude agents) — per-session earning, paused in the dashboard. Claude Code's agent view runs many background sessions at once and hides the per-session status line (our ad surface) while the dashboard is open. Both consequences are handled:

    • Per-session earning. Busy is keyed by session_id — which both the hooks and the status-line stdin carry — so the status line only accrues for the session it is actually rendering. A busy background session can't make an idle attached session earn, and a background Stop can't kill an attached session's earning (src/busy.ts markSessionBusy/isSessionBusy; the SubagentStart and SessionEnd hooks keep the per-session state keyed and cleaned up). The impression clock is likewise stored per session (files.ts readSessionClock/writeSessionClock) — critical because every session ticks the same status-line command, and with one shared clock an idle session's tick would reset a busy session's continuous-view progress before it could reach the 5 s window, so nobody would earn.
    • No earning in the dashboard. The dashboard renders no status line, so no ad is shown and the tick stops; the impression clock's 2 s stall-guard discards the in-progress window, so we never credit an unseen ad. Earning resumes when you attach to a working session. Proven end-to-end in test/agentview.test.ts.

Install

npx @idlepaydev/claude-code setup

This edits ~/.claude/settings.json:

  • sets statusLine to the render command with refreshInterval: 1 (backing up any existing statusLine to a idlepay-backup key, restored on remove);
  • appends an Idlepay hook entry (tagged with a marker) to each of the seven turn/session-lifecycle events, leaving any of your existing hooks intact;
  • installs the slash commands under ~/.claude/commands/ (each removed on remove, but only if it still carries our marker): /ad (+ /idlepayad), and the /idlepaysignin, /idlepaysignout, /idlepayrefresh, /idlepayupdate set.

Start (or restart) Claude Code. On first run the status line shows a sign-in code:

●  Idlepay · sign in at https://idlepay.dev/activate with code ABC-123

Activate it once and the machine token is saved to ~/.idlepay/credentials.json.

Commands (parity with the opencode /idlepay* family)

Sign-in is automatic (the status-line tick advances the device flow) and updates are automatic (see below), so these are explicit controls for when the automatic paths misbehave:

| Command | Does | | --- | --- | | /ad (alias /idlepayad) | Open the current sponsor in your browser and credit the click. | | /idlepaysignin | Start (or surface) the device-code sign-in; opens the page and shows the code. | | /idlepaysignout | Unlink this machine: delete credentials, reset runtime, drop the queue + clock. | | /idlepayrefresh | Force an immediate ad + earnings refresh, bypassing the 60s cadence. | | /idlepayupdate | Update to the latest version now (bypasses the 12h throttle). |

Updating

npx @idlepaydev/claude-code update

update re-fetches the package at @latest and re-runs its setup, which re-bakes the dist paths in settings.json (we wire absolute paths, not an npm spec, so "update" means fetch-newest-and-rewire).

The integration also checks for updates on its own — but only when you open a new window (the SessionStart hook), throttled to once / 12h via ~/.idlepay/cc-update-check.json. It deliberately does not check from the per-second status-line tick. You can also force it in-editor with /idlepayupdate. Either way the new version applies on the next Claude Code restart.

Remove everything we added (your prior statusLine/hooks are preserved):

npx @idlepaydev/claude-code remove

Earnings dashboard: https://idlepay.dev/dashboard.


Configuration

| Env var | Default | Purpose | | --- | --- | --- | | IDLEPAY_API | https://idlepay.dev | API base URL (point at staging for testing). |


Development

npm run build       # tsup → dist/{cli,statusline,hook}.js
npm run typecheck   # tsc --noEmit
npm run test        # vitest (pure logic only; no editor, no network)

Pure, unit-tested modules:

  • src/gates.tsgatesOpen, ImpressionClock (+ snapshot/restore for the one-shot process model).
  • src/busy.ts — the hook-event → busy/presence reducer and event parser.
  • src/render.ts — the status-line text formatter.
  • src/nudge.ts — the /ad-nudge state machine (advanceNudge, nudgeActive); pure (state + seq + now + rng → next state).
  • src/ad.ts — the /ad helper: selectAd (pure) + runAd with the browser open and the click report injected so the side effects are mockable.

Tests live in test/ (gates.test.ts, busy.test.ts, render.test.ts, nudge.test.ts, ad.test.ts) and mirror the opencode plugin's Vitest style. They cover the impression clock (including persistence and the over-long-gap guard), gatesOpen, receipt-seq idempotency/monotonicity, the busy reducer, line rendering (with and without the nudge), the nudge schedule (fires at the threshold, stays 3 s then hides, re-rolls 20-30 later, deterministic under a fixed rng), and the /ad helper (selection, the no-ad path, swallowed click failures).


What's needed to live-test later

Not done here (per the build constraints — no npm install, no editor launch):

  1. Install deps + build: from the repo root, npm install then npm run build --workspace packages/claude-code to produce dist/.
  2. Backend: point at staging with IDLEPAY_API=… (or the live API). The endpoints used are POST /api/device/{start,poll}, POST /api/ads/next, POST /api/telemetry/impressions, and GET /api/me/earnings.
  3. Wire it up: node packages/claude-code/dist/cli.js setup.
  4. Smoke-test the status line without Claude Code (the docs' mock-input trick):
    echo '{"session_id":"t","model":{"display_name":"Opus"},
      "workspace":{"current_dir":"/tmp"}}' \
      | node packages/claude-code/dist/statusline.js
    On first run this prints the sign-in line; after activating the code and a couple more invocations it prints the sponsored line.
  5. Smoke-test a hook:
    echo '{"hook_event_name":"UserPromptSubmit","session_id":"t"}' \
      | node packages/claude-code/dist/hook.js UserPromptSubmit
    # then inspect ~/.idlepay/runtime.json — busy should be true
    echo '{}' | node packages/claude-code/dist/hook.js Stop
    # busy should flip back to false
  6. Smoke-test the /ad helper (with an active ad in runtime.json):
    node packages/claude-code/dist/ad.js
    # opens the ad URL in your browser and prints "Opened <brand> ↗"
    # with no active ad it prints "No active ad right now."
  7. End-to-end in Claude Code: run setup, restart Claude Code, submit a prompt that keeps the agent busy for >5 s, and confirm: the line appears while busy, an impression is credited (visible on the dashboard), the line is absent when idle (killswitch off / no ad), the /ad command opens the sponsor and a click shows on the dashboard, the · ↗ /ad nudge appears briefly every 20-30 impressions, and remove cleanly reverts settings.json and deletes ~/.claude/commands/ad.md.

Things to verify on a real install that can't be unit-tested here: that refreshInterval: 1 actually produces ~1 s ticks during a busy span on the target Claude Code version; that the status-line command runs fast enough to not be cancelled (it makes at most one quick HTTP call per 60 s and otherwise only touches local files); that the Git Bash / PowerShell path handling on Windows resolves the node …/statusline.js command correctly; and that the /ad inline-bash injection runs the helper on the target Claude Code version.