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

looprc

v0.0.3

Published

Read your agent CLI session logs, score sessions for cost and friction, and turn expensive patterns into reusable skills, sub-agents, hooks, and instruction files.

Downloads

462

Readme

looprc

npm CI license

A local-first CLI that reads your agent CLI session logs, scores each session for cost and friction, and turns the expensive/friction-heavy patterns into reusable skills — then tells you whether those artifacts actually paid for themselves.

The question this answers: you wrote a skill — did it actually save you anything? Nothing else in this space closes that loop. Tools either count tokens (ccusage) or scaffold config (skill/agent generators). This does both and connects them.

Local-only. No API key required — distill shells out to your already-authenticated claude CLI. No network calls except reading files already on disk.

Status

The full v1 loop (adapters, cost, friction, distill, attribution) plus v2's Copilot adapter and real load-based attribution are built and cross-validated against real session data (see git log for how each phase was verified — cost was checked byte-for-byte against ccusage, friction and reach were validated against hand-read transcripts, distill and attribute were run end-to-end against real sessions with real claude CLI calls, and the Copilot adapter was validated against actual copilot -p output including a real bug it caught).

| Source | Status | |---|---| | Claude Code | Adapter built and validated against real ~/.claude/projects/**/*.jsonl logs across multiple real sessions, including a 65MB / 9000-record one. | | Copilot | Adapter built and validated against real ~/.copilot/session-state/**/events.jsonl output — including catching and fixing a real tool-result-ordering bug during validation. Usage figures match session.shutdown.modelMetrics exactly. | | Codex | Adapter built from the documented log-format spec, unverified — no real Codex session was available to test against. Treat its output with skepticism until checked against a real ~/.codex/sessions/*.jsonl file. |

Install

npx looprc --help      # no install, run once
npm install -g looprc  # or install globally

Or run from source:

git clone https://github.com/vsthakur101/agent.git looprc && cd looprc
npm install
npm run build
node dist/cli/index.js --help

# or during development, no build step:
npm run dev -- --help

Quickstart

looprc scan                        # what sessions exist on this machine
looprc summary                     # one-shot digest: cost, this week, top friction, skill reach
looprc sessions --source claude-code
looprc cost session                # $ per session, cross-checked against ccusage
looprc friction --top 5            # your roughest recent sessions
looprc reach                       # which skills are listed vs. ever invoked — the "dead config" report
looprc grep "auth bug"             # find the session where you talked about something, by content
looprc distill <sessionId>         # propose a skill from a session, diff + approve before anything is written
looprc attribute .claude/skills/x  # did that skill actually help, before vs. after
looprc hooks install               # tag future sessions with their real loaded config tree (better than the git-date guess)
looprc init                        # scaffold .looprc/skills/ with an example, so sync has something to compile immediately
looprc sync                        # compile .looprc/skills/*.md out to .claude/skills/, .github/prompts/, AGENTS.md

Commands

Every read command supports --json for scripting.

| Command | What it does | |---|---| | scan [--source] | Index sessions found on this machine. | | sessions [--source] [--since] [--repo] | List sessions with filters. | | summary [--source] [--since] [--repo] [--top N] | One-shot digest: total cost, this week's cost, top-N sessions by friction, overall skill reach — composes cost/friction/reach's existing metrics into one "what should I look at" view instead of running all three separately. | | show <sessionId> [--source] [--compare <sessionId>] [--friction] | Full normalized timeline for one session. --compare shows turns/tool usage/cost/friction for two sessions side by side instead. --friction shows only the turns a friction signal fired on (rework, correction, tool failure, retry loop, unverified edit, abandonment), annotated with why — the "where," not just the aggregate score. | | cost [session\|daily\|repo\|weekly] [--source] [--since] [--repo] [--fail-if-above N] | Dollar cost, computed from an offline pricing table (or a vendor-reported figure, for sources that provide one). Unpriced turns (unknown model) are counted, never silently folded into totals. weekly shows a sparkline + ISO-week trend instead of a ranking; --fail-if-above (weekly only) exits 1 if the most recent week's cost is over the given dollar amount — a CI gate. | | friction [--source] [--since] [--repo] [--top N] [--trend] [--fail-if-above N] | Ranks sessions by a 0–100 friction score: rework, corrections, tool failures, retry loops, unverified edits, abandonment. A proxy for a rough session, not a measure of response quality. --trend shows a sparkline + weekly average instead of a ranked list; --fail-if-above (with --trend) exits 1 if the most recent week's average score is over the given number — a CI gate. | | reach [--source] [--since] [--repo] | Which skills earn their tokens — listed (loaded into context every session) vs. actually invoked. Claude Code only; the signal doesn't exist elsewhere. | | grep <pattern> [--regex] [--source] [--since] [--repo] | Searches turn text across every discovered session for a pattern — case-insensitive literal substring by default, or a real regex with --regex. Prints session id, turn, and a highlighted snippet per hit. | | distill <sessionId> [--as skill\|prompt\|agent] [--yes] [--output-dir] | Analyzes a session and proposes a skill (auto-loaded into context), a prompt (a .github/prompts/*.prompt.md slash-command template, explicitly invoked), or a subagent (a .claude/agents/*.md delegatable persona with its own context window) via your claude CLI. Nothing is written until you see a diff and approve it (or pass --yes). | | distill --batch [--top N] [--source] [--repo] [--since] [--min-friction N] [--as ...] [--yes] [--output-dir] | Sweeps the top-N highest-friction sessions (default 5) through the same pipeline instead of one at a time. Prompts once upfront for how many real, separately-billed claude CLI calls are about to fire, then each proposal still goes through its own normal diff/approve gate — --yes skips both. Prints total LLM cost across the batch at the end. | | undo [--output-dir] | Reverts the last file distill wrote in a project. | | validate [--dir] [--target skill\|prompt\|agent\|all] | Lints existing .claude/skills/*/SKILL.md, .github/prompts/*.prompt.md, and/or .claude/agents/*.md files (frontmatter, kebab-case name, description length, body size) — not just ones distill/sync wrote. Description-length limits are per-target (agent descriptions legitimately run to ~2000 chars with embedded examples; skill/prompt stay capped at 500). Exits non-zero on issues, so it's CI-friendly. | | attribute <artifactPath> [--min-sessions N] | Before/after comparison of session metrics. Uses real load-tag data (see hooks install) once there's enough of it; falls back to a git-creation-date split otherwise. Refuses to draw a conclusion below the sample-size threshold (default 10 sessions per side) — shows the numbers, doesn't pretend they're proof. | | hooks install [--global] | Registers two hooks in .claude/settings.json: SessionStart tags each future session with a hash of the config tree (CLAUDE.md, .claude/skills/, .claude/agents/) present at start; InstructionsLoaded records real ground truth for which memory files Claude Code actually loaded (not just which existed on disk). Both power real load-based attribute instead of the date guess. | | hooks uninstall [--global] | Removes exactly the hooks hooks install added — other hooks on the same or different events, and unrelated settings keys, are left untouched. A no-op if they were never installed. | | init [--dir] | Scaffolds .looprc/skills/example-skill.md if .looprc/skills/ is empty or doesn't exist yet, so sync has something to compile on the very first run. A no-op (never overwrites) once any canonical source exists. | | sync [--dir] [--yes] [--strict] | Compiles .looprc/skills/*.md — a canonical, hand-edited, committed source — out to .claude/skills/<name>/SKILL.md, .github/prompts/<name>.prompt.md, and an idempotent fragment in AGENTS.md. Same diff/approve/write/undo gate as distill, one gate per generated file. A canonical source with malformed frontmatter is skipped with a warning by default (the other valid ones still sync); --strict turns that into a hard failure — exits 1 and writes nothing, for CI. | | cache clear | Deletes the on-disk session parse cache (~/.looprc/cache/), forcing a fresh reparse next run. |

Canonical source (sync)

Instead of hand-editing .claude/skills/x/SKILL.md and separately keeping .github/prompts/x.prompt.md and AGENTS.md in sync by hand, write once:

.looprc/
  skills/
    commit-messages.md   <- --- name / description --- + body, you edit this, commit it
looprc sync
  -> .claude/skills/commit-messages/SKILL.md
  -> .github/prompts/commit-messages.prompt.md
  -> AGENTS.md   (a marked, idempotent fragment — re-syncing updates only that block)

.looprc/ is not gitignored — it's meant to be committed, unlike the tool's own bookkeeping (undo log), which lives globally in ~/.looprc/. distill still writes straight to .claude/skills/ by default; sync is the opt-in workflow for keeping multiple targets from one source.

Caching

Every command that reads sessions goes through a disk cache (~/.looprc/cache/) keyed by file path + mtime + size, so unchanged session files aren't reparsed on every invocation. Real measurement on this machine's 65MB session: 0.29s cold → 0.07s warm for show; a friction scan across every discovered session: 0.37s cold → 0.12s warm. A cache entry also carries a schema-version tag, so a future adapter or normalizer bug fix (this project has shipped two) invalidates stale entries automatically instead of silently serving pre-fix output forever. looprc cache clear is the manual escape hatch if something still looks wrong.

Configuration

friction, attribute, and anything else that scores sessions by friction read an optional global config at ~/.looprc/config.json. Right now the only tunable is the friction score's weights (default: rework 25, corrections 20, tool failures 15, retry loops 15, unverified edits 10, context pressure 10, abandonment 5 — summing to 100 but not required to):

{
  "frictionWeights": {
    "reworkRate": 40,
    "toolFailureRate": 5
  }
}

Only the keys you set are overridden; everything else keeps its default. No file, invalid JSON, or an unrecognized shape all fall back to the defaults — the last two print a warning to stderr rather than silently guessing or crashing.

Architecture

adapters/     vendor-specific log parsing — the ONLY place that knows Claude Code's or Codex's JSONL shape
normalize/    RawEvent[] -> Session (source-agnostic: turn reconstruction, tool_use/tool_result pairing,
              sub-agent tree threading, cumulative-usage diffing)
schema/       zod schemas for the raw and normalized shapes
metrics/      cost, friction, attribution, git lookup — all operate on normalized Session objects only
artifacts/    distill pipeline: extract -> redact -> compress -> propose (LLM) -> render -> validate ->
              diff -> approve -> apply -> undo; tag.ts hashes the config tree for load-based attribution;
              sync.ts + canonicalSource.ts render .looprc/skills/*.md out to every target through the
              same apply() gate
llm/          headless bridge to the `claude` CLI (no API key, no new billing relationship)
cli/          commander-based command surface

The rule that keeps this maintainable: vendor-specific knowledge lives only in adapters/. When a log format changes, exactly one file changes. Everything downstream — metrics, friction, distill — only ever sees the normalized Session/Turn/ToolCall shape in schema/session.ts.

Known limitations

  • Codex adapter is unverified. No real Codex session data was available; it's built from the documented format plus defensive parsing (prefers an explicit token-delta field, falls back to diffing cumulative snapshots). Validate against a real rollout file before trusting its cost/friction output.
  • Pricing is Claude-only via table; Copilot uses real vendor-reported cost. Claude Code sessions are priced from a hardcoded table (metrics/pricing.ts). Copilot self-reports actual dollar cost per model in session.usage_checkpoint/session.shutdown's modelMetrics[model].requests.cost — the adapter diffs these cumulative snapshots and threads the real figure through as Turn.vendorCostUsd, which cost.ts prefers outright over any table lookup. No verified Codex/OpenAI pricing source exists, so Codex sessions still report token counts without a dollar figure rather than fabricate one.
  • reach is Claude Code only — it depends on the skill_listing attachment record Claude Code emits, which Codex doesn't have an equivalent of.
  • Load-based attribute only covers sessions after you run hooks install — tags aren't retroactive, so a brand-new install still falls back to the git-date proxy until enough tagged sessions accumulate on both sides of an artifact's presence/absence.
  • Friction and cost are proxies, not quality measures. Every friction number is a structural signal (rework, retries, tool failures) — never presented as "the agent did a bad job," because the transcript alone can't tell you that.
  • sync only covers skills. The plan's canonical-source idea extends to agents and hooks too; v1 of sync (like distill) only has a skills pipeline. A malformed .looprc/skills/*.md file (missing/broken frontmatter) is now surfaced by name (default: warns and skips it; --strict: fails and writes nothing) rather than silently vanishing from the output.

Development

npm test         # vitest
npm run typecheck
npm run lint      # oxlint
npm run build     # tsup -> dist/

Tests use hand-built fixtures shape-verified against real Claude Code/Codex records (tests/fixtures/), not live session data — real-session validation happens by hand during development and is recorded in commit history, not in the automated suite.

CI (.github/workflows/ci.yml) runs all four checks above on every push and PR to main.