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

@ccgrapher/adapter-claude-code

v0.4.0

Published

Turns Claude Code hook invocations into a ccgrapher trace, so a real agent session becomes a readable run.

Readme

@ccgrapher/adapter-claude-code

Turns Claude Code hook invocations into a ccgrapher trace, so a real agent session becomes a readable, auditable run.

npm i -g @ccgrapher/adapter-claude-code

Wire it into .claude/settings.json:

{
  "hooks": {
    "SessionStart": [{ "hooks": [{ "type": "command", "command": "ccg-claude-code-hook" }] }],
    "PostToolUse": [{ "hooks": [{ "type": "command", "command": "ccg-claude-code-hook" }] }],
    "SessionEnd": [{ "hooks": [{ "type": "command", "command": "ccg-claude-code-hook" }] }]
  }
}

Each invocation reads the hook payload as JSON on stdin and appends to runs/cc-<sessionId>.jsonl under the session's project directory. Set CCG_TRACE_DIR to put them somewhere else. Then read the run the same way as any other:

ccg trace stats runs/cc-<sessionId>.jsonl
ccg serve runs        # the canvas, live, while the session is still going

The run id equals the filename stem. That is not a convention, it is what makes the file servable: ccg serve streams <id>.jsonl and the fold keys on the id.

Why a package rather than a documented recipe

A hook is a separate short-lived process, one per event. A TraceWriter built the obvious way owns seq for its file, and seq is what orders a stream whose timestamps collide and what a reconnecting reader resumes from — so a hook that constructed one fresh each time would write 0, 0, 0 and produce a file whose order cannot be recovered. Continuing from the tail fixes that, and then two tool calls finishing at once reintroduce it, because both read the same tail and pick the same number. So every append reads and writes under an exclusively-created <runId>.jsonl.lock, with a bounded wait and a reclaim for a lock left behind by a process that died holding it.

None of that is paste-able, and a recipe that waved at it would teach people to write traces that violate the contract they are supposed to be honouring.

What it records

| Hook | Event | | --- | --- | | SessionStart | run_started, source claude-code-hooks, spec.name from the project directory, then one capability_available per capability the payload names | | PostToolUse on mcp__<server>__<tool> | capability_invoked, id mcp:<server>/<tool> | | PostToolUse on Skill | capability_invoked, id skill:<name> | | PostToolUse on Task | capability_invoked, id agent:<type> | | PostToolUse on an MCP tool that could not reach its server | capability_lost, with the message as reason | | SessionEnd | run_finished, duration measured from the file's first event |

Everything else — PreToolUse, UserPromptSubmit, Stop, Notification, PreCompact — writes nothing. This contract has no word for them, and padding a trace with events that mean nothing is not recording more, it is recording worse.

Invocations carry no node. A hook knows a tool ran; it has no spec to attribute it to, and the contract makes node optional for exactly this reason. So a hook trace shows what a session had and what it used, and does not pretend to know which step of a workflow used it.

Honest limits

  • The connection-class rule is a heuristic. Claude Code does not tell a hook why a tool failed in any structured way, so capability_lost is decided by reading the error message for phrases like "connection closed" or "socket hang up". Those are written for humans, differ between MCP transports, and nobody promised to keep them stable. A phrase drifts and a lost server goes unrecorded. A failure that reaches the server and is refused by it stays an invocation, because the capability was there and was used.
  • SessionStart capabilities are a guess. The documented payload carries no inventory of servers, tools or skills at all. The adapter reads several plausible fields in case a future version sends one, and most sessions will match none of them and report nothing. That is the honest answer, and the contract is explicit about how to read it: silence is not absence. A run reporting no availability is a run where nobody looked, not a run where nothing was there. A server listed without its tools contributes nothing, because mcp:<server> is not an id in this vocabulary and would never match an invocation.
  • A session whose start was never seen gets no finish. Install the hooks halfway through a session and SessionEnd writes nothing, because the only duration available would be a zero meaning "nobody measured".
  • Names come from inputs. A Skill or Task call whose input does not name the skill or subagent is not recorded, rather than recorded as skill:unknown.
  • The adapter can fail, and says so. It exits 1 with a message on stderr, never 2 — Claude Code reads 2 as "block this", and something that only records a session must never be able to change it.
  • One session, one file. Resuming a session appends to the same run, which is usually what you want; two concurrent sessions sharing a session id would not be, and nothing here can tell them apart.

API

The mapping is exported, so it can be tested and reused without spawning a process:

import { eventsForHook, appendHook, capabilityForTool } from "@ccgrapher/adapter-claude-code";

capabilityForTool("mcp__linear__issues_list"); // "mcp:linear/issues_list"
eventsForHook(payload, { startedAt });         // pure: payload in, events out
appendHook(payload, { dir: "runs" });          // resume, lock, append

Part of ccgrapher. Apache-2.0 — see LICENSE and NOTICE.