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/trace

v0.4.0

Published

The versioned JSONL event contract for workflow runs, and the pure fold that turns it into state.

Downloads

67

Readme

@ccgrapher/trace

The versioned JSONL event contract for workflow runs, and the pure fold that turns it into state.

import { parseTraceLine, reduceRun, emptyRunState } from "@ccgrapher/trace";
import { TraceWriter, readTrace, followTrace } from "@ccgrapher/trace/node";

const state = readTrace("run.jsonl").reduce(reduceRun, emptyRunState("run-1"));

Every event carries the same envelope — { v: 1, type, runId, seq, ts } — and v: 1 is a promise: within it the contract is additive only, forever. New optional fields and new event types may appear, nothing is removed or narrowed. parseTraceLine never throws, on malformed JSON or on a type it has never heard of, which is what lets an old reader sit in front of a new writer.

reduceRun is the one fold a canvas, a CLI progress display and a hosted dashboard all bind to, so the three of them cannot quietly disagree about whether a run is finished.

Every token and cost field is optional, and absent means unknown. Render it "n/a", never 0. A zero that really means nobody measured is the dishonesty this project exists to catch.

Capability events

Three types say what the runtime had to work with: capability_available and capability_lost are run-scoped, because whether a thing exists is a fact about the environment rather than about any one step. capability_invoked carries an optional node: a runner knows which node called, while an adapter watching a live agent session knows a tool was used and has no spec to attribute it to. Both are telling the truth, and requiring a node id would have excluded the second while calling the contract general.

The same rule as tokens and cost applies, and matters more here: a capability nothing reported on is unknown, not absent. RunState.capabilities keeps available tri-state so a reader cannot collapse the two.

capability_denied does not exist. Nothing can honestly produce one yet, and adding it can wait until something can.

What an old reader does with them

These are the additive change v: 1 promised, and they were the first test of it. An older reduceRun folds them as no update. Specs carrying uses: still parse under older tools, which strip the field silently.

One version degrades rather than ignores. ccg serve at 0.3.1 and earlier named each SSE message after its event type and forwarded only types its own schema knew, re-emitting the rest as ccg.unparsed — and because an unparsed line carries no seq, withholding it entirely on a Last-Event-ID resume. So a newer writer's events reached an older canvas as nothing at all. From 0.4.0 the server gates structurally instead: anything carrying a seq is forwarded with its id, whether or not this server has heard of it.

Writers are the other way round. TraceWriter parses what it is given, so an older writer cannot emit a type it does not know. Writers upgrade to speak; readers never had to.

The main entry is bundler-safe. Anything touching the filesystem lives at @ccgrapher/trace/node.


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