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

ccline-cli

v0.2.0

Published

A fast, fail-soft statusline toolkit for Claude Code.

Readme


Quickstart

npx ccline-cli install

That merges one key into .claude/settings.json, and leaves every other key in the file exactly as it was:

{"statusLine": {"type": "command", "command": "npx ccline-cli statusline"}}

Start a new Claude Code session and the line is there:

ctx 78% left · 811.3k tok · Opus 5 · main · $2.14 · 18m

Context left, not context used, because a budget you spend down is a number you can act on mid-task. Then real tokens, the model, your branch, the session cost, and how long you have been at it.

Three commands worth knowing next:

ccline config     # pick segments, order and theme, with a live preview
ccline preview    # render your line with sample data
ccline doctor     # explain why the line is not what you expected
ccline pets       # the optional animals, and what each pose looks like

Nothing is taken over behind your back. If another tool already owns your statusline, install stops and tells you rather than replacing it, and ccline uninstall removes the key only if the command in it is still ccline's.

The line

When the window is nearly gone, the line says so:

NO_COLOR is honoured:

And you can cut it down to whatever you actually read:

Every image above was produced by running the renderer. They are captured from real output by npm run assets, and a test fails if any of them stops matching what the tool prints. See screenshots are not mockups.

Two things this project takes seriously

The render path never fails loudly. ccline statusline never exits non-zero and never writes to stderr, because a broken statusline must not touch your session. Every segment is independently fail-soft: one that throws, or that overruns the shared budget, drops itself and the rest of the line still renders.

Slow is the same as broken. A statusline that misses its deadline renders empty and looks perfectly healthy while telling you nothing. So the budget is asserted by a test, and that test also asserts the line was not empty, because otherwise the timing assertion would pass on a line that rendered nothing.

Both rules, and what they cost, are in docs/ARCHITECTURE.md.


The four surfaces

1. Statusline

Every segment is optional and reorderable. The table is everything that ships, and the last row is registered but off until you ask for it.

| Segment | Renders | Notes | |---|---|---| | context | ctx 37% left | Remaining, not used. Yellow at 40 left, red at 15 | | tokens | 811.3k tok | Input + output. Cache reads excluded by default | | model | Opus 5 | From the payload. Truncated past 32 characters | | branch | main | Read from .git/HEAD, no git process spawned | | cost | $2.14 | From the payload. Gray until $5, then yellow | | duration | 18m | Since the session's first message | | update | ↑ 0.2.0 | Only when a newer version is known. Nothing when current, and no network from the render path | | pet | | Off by default. One cell of animal, reacting to the session. Six ship, ccline pets lists them |

$ ccline segments
Segments, in render order.

  context   on 1     Context window remaining, e.g. ctx 37% left
                       options.context.warnBelow = 40  Turn yellow below this much remaining
                       options.context.dangerBelow = 15  Turn red below this much remaining
  tokens    on 2     Input plus output tokens this session, cache reads excluded
                       options.tokens.includeCache = false  Add cache read and creation tokens to the total
  model     on 3     The model display name
                       options.model.maxLength = 32  Truncate longer names
  branch    on 4     Git branch, read from .git/HEAD without spawning git
                       options.branch.maxLength = 28  Truncate longer branch names
  cost      on 5     Session cost in USD
                       options.cost.noticeAbove = 5  Turn yellow at or above this many dollars
  duration  on 6     Time since the session's first message
  update    on 7     A newer published version, when one is known. Never touches the network
                       options.update.staleAfterHours = 168  Stop showing a check older than this
  pet       off      A pet in one terminal cell, reacting to the session. Off unless you enable it
                       options.pet.name = "bubba"  Which pet to draw
                       options.pet.cells = 1  Terminal cells to occupy, 1 or 2
                       options.pet.alertBelow = 15  Go alert below this much context remaining
                       options.pet.appearWithinMs = 30000  Show the arriving pose for this long into a session
                       options.pet.longTurnMs = 45000  Flick the tail after this long with no new message
                       options.pet.sleepAfterMs = 300000  Fall asleep after this long with no new message

Order comes from "segments" in your config, not from the list above.
Add your own: createRegistry() from "ccline-cli/segments" takes { name, summary, render }.
Config read from: defaults only

It also flags a configured name that nothing implements, which is the one mistake the render path can never report:

  brnach    unknown  no implementation registered, renders nothing

On the render path that typo is invisible by construction: it looks exactly like a segment with nothing to say.

Two numbers worth understanding. Tokens are deduplicated by message.id, because Claude Code writes one assistant message as several transcript lines that each repeat the same cumulative usage. Summing them naively inflates the total by about 1.9x (measured on a real 2232-line transcript: 1,056,805,602 against a true 534,220,602). And cache reads are excluded from the headline figure, because on a real session input plus output was 811,270 against 348,167,965 cache reads. A number dominated 429 to 1 by cache traffic is not a number about your work. Turn it back on with options.tokens.includeCache.

Pets

Six animals ship, each living in one terminal cell and changing pose with the session. They are off by default and stay off until you name one:

{
  "segments": ["pet", "context", "tokens", "model", "branch", "cost", "duration"],
  "options": { "pet": { "name": "bubba" } }
}

The pose is the point. It is a pure function of the payload and the transcript stats, so the same session state always draws the same picture and the line never flickers, and it rides the single memoised transcript read that tokens and duration already make rather than adding a second one. Five states, and each of these lines is a real render of the session beside it:

| Pose | State | When | Colour | |---|---|---|---| | arriving | appear | the first 30 seconds of a session | green | | alert | alert | context at or below 15% remaining | yellow | | tail flick | tailflick | 45 seconds into a turn with no new message | cyan | | asleep | sleep | five minutes with nothing happening | dim | | sitting | idle | everything else | gray |

Alert outranks every other pose, because it is the only one carrying information you have to act on, and it fires at the same threshold that turns the context number red. The rest is decoration, and decoration must never outrank a signal.

$ ccline pets
Pets, one terminal cell each.

  bubba  The reference cat, sitting in one terminal cell
         appear ▐   alert ▀   tailflick ▙   sleep ▄   idle █
  crab   A crab, claws up and shell low, scuttling sideways
         appear ▐   alert ▀   tailflick ▌   sleep ▄   idle █
  dev    The human at the other end, in a long coat
         appear ▐   alert ▀   tailflick ▌   sleep ▄   idle █
  duck   A duck, flat bill and low body, waddling in one terminal cell
         appear ▐   alert █   tailflick ▙   sleep ▄   idle ▟
  fox    A fox, mostly tail, facing left
         appear ▐   alert ▀   tailflick ▟   sleep ▄   idle █
  ghost  A small sheeted spook, bobbing where the cat would sit
         appear ▐   alert ▀   tailflick ▜   sleep ▄   idle █

The pet segment is OFF by default. Turn it on by naming it in "segments":
  {"segments": ["context", "pet", "model"], "options": {"pet": {"name": "bubba"}}}

"ccline pets <name>" draws every pose 1:1. Adding one: docs/PETS.md.

ccline pets <name> draws every pose at one pixel per cell, next to the glyph it reduces to. A terminal that cannot render block drawing characters gets no pet at all, rather than an ASCII face: at one cell wide the alternatives are a period or a hash, and a stray period in a statusline reads as a bug, not as a cat.

Drawing one is the easiest first contribution here, and docs/PETS.md is the whole procedure: the frame contract, why one cell holds only sixteen distinguishable pictures, and a complete worked example you can follow without reading any source.

2. Hooks

Four hooks, each installed on the events it needs.

$ ccline hooks list
ccline hooks (project: /your/project/.claude/settings.json)

  notify       not installed
               Desktop notification when Claude finishes or needs permission.

  session-log  not installed
               Append one JSONL record per session to ~/.ccline/sessions.jsonl.

  auto-format  not installed
               Run the project's own formatter on files Claude edits.

  guard        not installed
               Block edits to paths you marked protected.
$ ccline hooks add guard
installed guard in /your/project/.claude/settings.json
  runs on: PreToolUse

| Hook | Event | What it does | |---|---|---| | notify | Stop, Notification | osascript / notify-send / PowerShell, and nothing at all when none is available. Re-entrant stops are ignored, so a session that keeps going does not spam you | | session-log | SessionEnd | One JSONL row per session: project, model, branch, duration, and four separate token counters | | auto-format | PostToolUse | Runs prettier, biome or dprint only if the project already depends on it and the binary is in node_modules. Formats the edited file, never the repo, and never installs anything | | guard | PreToolUse | Denies edits to protected paths, with a reason |

guard is the one with teeth. It answers with Claude Code's decision object and exit code 0, so "blocked" and "crashed" are never the same observable event:

$ echo '{"hook_event_name":"PreToolUse","tool_name":"Write","tool_input":{"file_path":"/p/.env"},"cwd":"/p"}' | ccline hook guard
{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"deny","permissionDecisionReason":"ccline guard: .env is protected by the pattern \".env\". Edit it yourself, or remove the pattern from options.guard.protect in your ccline config."}}

An allowed path prints nothing and exits 0. So does malformed input. The defaults cover .env, .env.*, *.pem, *.key, id_rsa, id_ed25519, .git/** and .ssh/**, with .env.example and its siblings carved back out because those exist to be committed and edited. Add your own with options.guard.protect, or drop the defaults with options.guard.useDefaults: false.

Scopes work the same everywhere in ccline:

ccline hooks add notify --project   # .claude/settings.json, shared with the repo (default)
ccline hooks add notify --local     # .claude/settings.local.json, yours, gitignored
ccline hooks add notify --user      # ~/.claude/settings.json, every project

session-log installs on SessionEnd, not Stop. Stop fires once per turn and would write dozens of rows per session. The cost is that a session killed by closing the terminal never fires SessionEnd and gets no row.

3. Slash commands

Four prompts, installed as markdown into .claude/commands.

$ ccline commands add all
Project commands  /your/project/.claude/commands

  created    review.md
  created    ship.md
  created    catchup.md
  created    explain.md

| Command | What it asks for | |---|---| | /review | Defects, not taste. Names what to skip, and demands a concrete failing case or no finding | | /ship | Pre-flight: tests, types, tree, branch. Reports, never pushes. Never passes a check it did not run | | /catchup | What changed since a ref, and why. Says "reason not recorded" instead of inventing one | | /explain | A file or symbol, for someone seeing it for the first time |

Run it twice and nothing is written:

$ ccline commands add all
Project commands  /your/project/.claude/commands

  unchanged  review.md
  unchanged  ship.md
  unchanged  catchup.md
  unchanged  explain.md

Ownership lives in the file, as a <!-- ccline:managed ... --> marker, not in a manifest that goes stale the moment you delete a file by hand. A file ccline did not write is never overwritten (refused-foreign), and a file it wrote that you then edited is not either (refused-modified). Both exit 1, say so on stderr, and leave the bytes identical. --force is the only way past.

Every shipped command grants read-only git in allowed-tools, and a test fails any bare Bash grant. A shipped prompt that can run anything is a blank cheque on your machine.

4. Output styles

$ ccline styles list
Output styles (project scope)
  directory: /your/project/.claude/output-styles
  settings:  /your/project/.claude/settings.json

    concise   available  Short answers with no preamble. Code first, prose only where it changes a decision.
    teaching  available  Explains the why, names the tradeoff, and assumes a reader who wants to learn.
    review    available  Adversarial reviewer. Looks for what is wrong before what is right.

Active style: none, Claude Code is using its default.
Switch with "ccline styles use <name>".
$ ccline styles use review
Installed review to /your/project/.claude/output-styles/review.md
Active output style is now "review" (/your/project/.claude/settings.json)

ccline styles off clears it, ccline styles remove <name> deletes the file, and removing a style clears the active setting only when it was that style. Full list: ccline styles help.


Configuration

Three layers, each overriding the one before:

  1. built-in defaults
  2. ~/.ccline/config.json, yours, every project
  3. the nearest .ccline.json, searched upward from the session's directory
{
  "segments": ["context", "tokens", "model", "branch", "cost", "duration", "update"],
  "separator": " · ",
  "color": true,
  "theme": "default",
  "budgetMs": 300,
  "update": { "check": true, "everyHours": 24 },
  "options": {
    "context": { "warnBelow": 40, "dangerBelow": 15 },
    "tokens": { "includeCache": false },
    "model": { "maxLength": 32 },
    "branch": { "maxLength": 28 },
    "cost": { "noticeAbove": 5 },
    "update": { "staleAfterHours": 168 },
    "guard": { "protect": ["infra/**"], "useDefaults": true }
  }
}

| Field | Type | Default | Meaning | |---|---|---|---| | segments | string[] | the seven on by default above | Which segments render, and in what order. Duplicates and unknown names are dropped | | separator | string | " · " | Joined between rendered segments | | color | boolean | true | NO_COLOR still wins over a true here | | theme | string | "default" | default, bright, ocean, sunset, plain. plain means no colour | | budgetMs | number | 300 | Wall-clock budget for the whole render, shared by all segments. Clamped to 5000 | | update | object | { "check": true, "everyHours": 24 } | Whether a command may ask npm for a newer version, and how often. Never the statusline. See the update check | | options | object | {} | Per-segment option bags, keyed by segment name |

Every field validates and repairs. A bad value falls back to the default, a bad file falls back to the layer below it. Nothing in the config path throws, because a statusline that dies over a trailing comma is a statusline that ate your session.

A project .ccline.json is opt-in, and it can only change how the line looks. ccline will not load a plugin module named in a config file: that file arrives with a git clone, so honouring a module path in it would mean cloning a repo executes its code on your next prompt render.

For the same reason, a project .ccline.json may turn the update check off but never back on. A repository you cloned does not get to make a network request happen on a machine whose owner switched that off.

The update check

The update segment shows ↑ 0.2.0 when a newer ccline has been published. Getting that answer needs one HTTP request, and the render path never makes one. The two halves are completely separate, and the seam between them is a file:

a command you typed          ~/.ccline/cache.json          every prompt render
  ccline install ─┐                                                 │
  ccline doctor  ─┼─► check ─► GET dist-tags ─► {checkedAt,     ┌────┘
  ccline hooks   ─┘   (≤1/day)                   latest,        │
                                                 current} ──────► update segment
                                                                  (reads, never asks)

Exactly when it runs. Only from one of these commands, and only if the last check was more than update.everyHours (24 by default) ago:

install   uninstall   config   commands   hooks   preview   segments   pets   styles   doctor

Two commands are deliberately absent and never will be: statusline, which runs on every prompt render, and hook, which runs on every tool use and is invoked by Claude Code rather than by a person. version, help and the bare ccline banner are out too, so a script that shells out for a version number never starts anything.

Exactly what is sent.

GET https://registry.npmjs.org/-/package/ccline-cli/dist-tags
accept: application/json
user-agent: ccline-cli/0.1.0

That is the entire request. No body, no cookies, no other headers. No session id, no machine id, no paths, no code, no usage, no telemetry of any kind. The reply today is 18 bytes, {"latest":"0.1.0"}, counted as it arrives and abandoned past 64KB or 3 seconds, whichever comes first.

The registry learns an IP address and that something asked about this package, which is exactly what npm view ccline-cli tells it. The small dist-tags endpoint is asked rather than registry.npmjs.org/ccline-cli, which is the full packument: 27KB here today, and 15MB for a package with real history, to extract one string on somebody's tethered laptop.

The first run only announces. It prints a short note to stderr (so --json output stays parseable) saying a check will happen, and makes no request at all. Nothing is asked until a previous run has said it is coming, and the announcement is only remembered if the note could be recorded, so a read-only ~/.ccline never gets past this step.

Turn it off in any one of three ways. Any single one is enough, and there is no precedence in which one overrides another:

CCLINE_NO_UPDATE_CHECK=1          # environment, any value except "", "0", "false"
ccline doctor --no-update-check   # one invocation, accepted by every command
{ "update": { "check": false } }

With any of them set, no request is made, no note is printed, and the segment renders nothing. A project .ccline.json may turn the check off but never back on: that file arrives with a git clone, and a repository you cloned does not get to make a network request happen on a machine whose owner switched it off.

It fails to silence, never to noise. Offline, behind a proxy, a 404, a 500, a captive portal's HTML, a body that is not JSON, a latest that is a tag name rather than a version: every one becomes "unknown", and unknown renders nothing. ccline doctor is where the four different silences are told apart, including when the last check ran, when the next is due, and why an answer is stale. Point CCLINE_REGISTRY at a mirror if you have one.

The interactive picker

ccline config

Checkboxes for segments, reordering, a theme list, and a live preview of the real line through the real renderer. Enter writes ~/.ccline/config.json, escape writes nothing at all. It preserves keys it does not manage (budgetMs, options, and anything a future version adds), and it refuses a config.json with a syntax error rather than flattening it.

It needs a terminal, and says so instead of hanging when it does not have one:

$ ccline config < /dev/null
ccline config needs an interactive terminal.
Run it directly in a shell, or edit /home/you/.ccline/config.json by hand.
"ccline segments" lists the names, "ccline preview" shows the line.

Writing your own

Full guide for all four surfaces: docs/PLUGINS.md. The short version for segments, which is the extension point most people want.

A segment is a function from the parsed payload to text, or to nothing.

// pomodoro.mjs
import { createRegistry } from "ccline-cli/segments";
import { renderLine } from "ccline-cli/render";
import { parsePayload, toCtx } from "ccline-cli/payload";
import { loadConfig } from "ccline-cli/config";

const registry = createRegistry([
  {
    name: "pomodoro",
    summary: "Minutes left in the current 25 minute block",
    render: (ctx) => {
      const left = 25 - Math.floor((ctx.now / 60000) % 25);
      return { text: `${left}m block`, color: left <= 5 ? "yellow" : "gray" };
    },
  },
]);

let raw = "";
for await (const chunk of process.stdin) raw += chunk;

const payload = parsePayload(raw);
const base = loadConfig({ cwd: payload.cwd ?? process.cwd(), env: process.env });
const config = { ...base, segments: ["context", "pomodoro", "model"] };
const ctx = toCtx(payload, { config, env: process.env, now: Date.now() });

const line = await renderLine(ctx, registry);
if (line !== "") console.log(line);
$ echo '{"model":{"display_name":"Opus 5"},"context_window":{"used_percentage":30}}' | node pomodoro.mjs
ctx 70% left · 22m block · Opus 5

Point Claude Code at it and it is your statusline:

{"statusLine": {"type": "command", "command": "node /abs/path/pomodoro.mjs"}}

Three rules, two of which the renderer enforces for you:

  • Returning null is normal. No data is not an error. Drop yourself.
  • Throwing is survivable. A segment that throws or rejects drops itself and nothing else, so you do not need a try/catch for correctness.
  • Do expensive work async. This one is yours. No deadline in JavaScript can interrupt a synchronous loop, so a busy sync segment blocks the prompt no matter what budgetMs says.

A later spec replaces an earlier one of the same name, so overriding a built-in is the same operation as adding a new one.


Troubleshooting

Start here. The render path is silent by contract, so doctor is where ccline explains itself.

ccline doctor

It runs a check per concern and tells you which one is the reason:

ccline doctor 0.1.0
[ ok ] Statusline installation
       ccline runs your statusline, from /your/project/.claude/settings.json:
         npx ccline-cli statusline
[info] Installed invocation
       The command runs through npx unpinned, so each render resolves whatever is
       latest on the registry rather than this copy. This ccline is 0.1.0.
[fail] Transcript and checkpoint
       No transcript directory for this working directory:
         ~/.claude/projects/-your-project
       Either Claude Code has never run here, or CLAUDE_CONFIG_DIR points
       somewhere other than where Claude Code writes. tokens and duration
       cannot render without it.
[warn] Segment "tokens"
       Renders nothing. No transcript could be read, and tokens are not in the
       payload at all, so there is no source for this number.
[warn] Update check
       ccline 0.2.0 is published and you are running 0.1.0. The last check ran
       3 hours ago, and the next one is due in 21 hours.
       The "update" segment is showing ↑ 0.2.0 on your line.
       fix: npm install -g ccline-cli@latest
[ ok ] Render time
       Rendered in 1ms against a 300ms budget, 100% headroom.

Your line right now:
  ctx 37% left · 4.8M tok · Opus 5 · main · $12.40 · 26d 1h

It exits non-zero when something is actually wrong, so it works in a script, and ccline doctor --json gives the same report as JSON.

| Symptom | Likely cause | |---|---| | No line at all | Nothing installed, or a higher-precedence settings file shadows yours. Claude Code reads settings.local.json over settings.json over ~/.claude/settings.json. Doctor names the winner, and install warns at the time | | Line appeared, then stopped | The installed command points at a path that no longer exists. A failing statusline is silent by design, so this looks like nothing at all | | tokens and duration missing | No readable transcript. They are the only two segments that need one, and tokens are not in the payload | | First render short, later ones complete | Expected on a very large transcript. The cold parse cannot finish inside the budget, so those segments drop once, the checkpoint lands, and every later render is complete | | No colour | NO_COLOR is set, "color": false, or "theme": "plain". Doctor reads all three | | Changed settings, nothing happened | Claude Code reads settings.json at session start. Start a new session | | update never shows anything | Usually because you are on the latest version, which is the point. ccline doctor says which of the four silent cases it is: up to date, never checked, checked and got no answer, or an answer too old to trust |

Doctor can also prove it is not lying to you:

$ ccline doctor --self-test
25/25 checks proved they can fail.

That feeds fabricated broken worlds through the real check functions and asserts each one reports a problem. A check that cannot fail is indistinguishable from a check that passes, which is the failure this whole project is organised around.

Screenshots are not mockups

Every image in this README is generated:

npm run assets         # regenerate the mark and re-capture the screenshots
npm run assets:check   # fail if anything committed no longer matches

scripts/gen-screenshots.mjs runs the real renderer over pinned fixtures and converts the escape codes it actually emitted, including the pet cards, so the poses in the images are the poses the segment produced for those sessions. scripts/gen-mark.mjs draws the cat from a pixel grid, and scripts/gen-pets.mjs draws its five poses into src/pets/frames.ts. No output is edited by hand, npm test fails if one drifts from its source, and CI runs the check on every push.

Contributing

Yes please. CONTRIBUTING.md has the layout, the test bar and what a good PR looks like. The short version:

git clone https://github.com/br9704/ccline.git
cd ccline
npm install
npm test

A new segment is the easiest first PR, and docs/PLUGINS.md walks the whole thing.

One rule stricter here than in most repos: a test must assert the failing case. Proving a guard passes when things are fine says nothing. Prove it fires when they are not.

The mark

This is Bubba. He is a real cat, recoloured orange and white, and he is drawn from a 64x64 pixel grid rather than stored as a picture, so the geometry has one source and cannot be nudged out of shape by accident. assets/logo.svg is the bare mark, assets/favicon.svg is the 16px version on its own plate, and node scripts/gen-mark.mjs --preview prints the whole grid in your terminal.

Licence and credits

Code: MIT. Use it, fork it, ship it.

The cat is not. The mark is Bubba, and the artwork in assets/ plus the pixel grids in scripts/gen-mark.mjs are © Bruno Jaamaa, all rights reserved. Fork the code, draw your own pet.

Built by the team behind Aethereum, which gives a team's AI coding agents a shared brain. ccline does not depend on it, does not talk to it, and never phones home. If you have never heard of Aethereum, nothing in this tool changes.