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

@latitude-data/claude-code-telemetry

v0.0.7

Published

Claude Code Stop-hook that streams session transcripts to Latitude as OTLP traces

Readme

@latitude-data/claude-code-telemetry

Claude Code Stop hook that streams session transcripts to Latitude as OTLP traces. Full conversation fidelity — user prompts, assistant responses, and tool I/O — plus the real system prompt and tool definitions that reached the model, without the truncation and flag combinatorics of Claude Code's native OpenTelemetry path.

Install

npx -y @latitude-data/claude-code-telemetry install

The installer walks you through the setup: prompts for your Latitude API key and project slug, wires the Stop hook into ~/.claude/settings.json, and — on macOS — sets up BUN_OPTIONS via launchctl with a persistent LaunchAgents plist so the preload works for both terminal claude and the Claude Desktop app.

Then fully quit and relaunch claude for the preload to attach:

  • Terminal — open a new terminal window.
  • Claude Desktop — ⌘Q to quit, then relaunch from Dock or Finder.

That's it. Trace runs show up at https://console.latitude.so/projects/<your-slug>.

Install flags

| Flag | What it does | | --- | --- | | --api-key=<key> | Pass the API key instead of being prompted. | | --project=<slug> | Pass the project slug instead of being prompted. | | --staging | Target https://staging.latitude.so / https://staging-ingest.latitude.so. | | --dev | Target http://localhost:3000 / http://localhost:3002. | | --no-launchctl | Write BUN_OPTIONS into settings.json instead of setting it via launchctl. Useful if you only use terminal claude and don't want anything installed under ~/Library/LaunchAgents/. | | --yes / --no-prompt | Skip all prompts. Required for non-TTY / CI invocations. |

Re-running install is idempotent — existing values in settings.json are shown as defaults and the Stop hook isn't duplicated.

Uninstall

npx -y @latitude-data/claude-code-telemetry uninstall

Shows a plan and asks for confirmation before touching anything. Reverses only what the installer did:

  • Removes LATITUDE_* and (if it's ours) BUN_OPTIONS from ~/.claude/settings.json (backup at settings.json.latitude-bak).
  • Removes the Stop hook entry — leaves any other hooks alone.
  • launchctl unsetenv BUN_OPTIONS only if it still points at our preload.
  • Unloads and removes ~/Library/LaunchAgents/so.latitude.claude-code-telemetry.plist.
  • Deletes ~/.claude/state/latitude/ (preload, state, captured request files).

What gets sent

For each turn, the hook emits one trace with three span kinds:

  • interaction — the user prompt and turn-level timing / counts.
  • llm_request — one per model call. Carries the model name, token usage (input/output/cache), input and output messages, and — when the preload is active — the full system prompt (gen_ai.system_instructions), tool schemas (gen_ai.tool.definitions), and exact request parameters. A tool-loop turn produces N sibling llm_request spans, one per distinct assistant message.id.
  • tool_execution — one per tool call. Canonical gen_ai.tool.* attributes: name, call.id, call.arguments, call.result. Failures set error.type and OTel status code 2.

All spans share the session id so they group together in the Latitude UI.

How it works

Two parts, and the installer wires both up:

1. Stop hook → OTLP export

Claude Code's Stop hook fires at the end of each turn. Our hook reads the new rows of the session's JSONL transcript, reconstructs user → assistant → tool_use → tool_result turns, and POSTs OTLP spans to ${LATITUDE_BASE_URL}/v1/traces. State lives at ~/.claude/state/latitude/state.json so each invocation only processes new lines.

This alone gets you conversation-level fidelity. The transcript, however, doesn't contain the system prompt or the tool definitions that reached the model — those are composed by claude at runtime and never written to disk.

2. Bun --preload fetch intercept (optional but installed by default)

The installer ships a small ESM module (~/.claude/state/latitude/intercept.js) and wires BUN_OPTIONS=--preload=<path> so Bun loads it into the claude process. Inside claude, it wraps globalThis.fetch, tees the response of every POST to Anthropic's /v1/messages, and — the moment the first message_start SSE event arrives — writes the full request body to ~/.claude/state/latitude/requests/<message_id>.json.

The Stop hook then matches those files to their assistant calls by message_id and enriches each llm_request span with:

  • gen_ai.system_instructions — the full composed system prompt.
  • gen_ai.tool.definitions — every tool schema offered to the model.
  • gen_ai.request.model / max_tokens / temperature / top_p / stream.
  • gen_ai.input.messages — the real message array, overriding the transcript reconstruction.
  • llm_request.captured = "true" — marker so you can filter for enriched spans.

Request files are deleted after the hook consumes them, and anything older than 24h is swept on each run.

Why launchctl on macOS

Claude Desktop doesn't forward env from ~/.claude/settings.json to the claude subprocess it spawns — that field only reaches hook subprocesses. So setting BUN_OPTIONS there works for terminal claude but is silently ignored by Desktop. launchctl setenv puts the var in launchd's environment, which the GUI app inherits. The installer also writes a LaunchAgents plist so this survives reboots.

Configuration reference

Everything the installer writes. Edit ~/.claude/settings.json directly if you want to tweak:

env

| Variable | Required | Default | Description | | --- | --- | --- | --- | | LATITUDE_API_KEY | yes | — | Bearer token for Latitude ingestion. | | LATITUDE_PROJECT | yes | — | Slug of the project to route traces into. | | LATITUDE_BASE_URL | no | https://ingest.latitude.so | Override ingest origin. Installer sets this only when you pass --staging or --dev. | | BUN_OPTIONS | written when launchctl isn't used (all non-macOS platforms, or macOS with --no-launchctl) | — | --preload=<absolute-path-to-intercept.js>. See "how it works" above. | | LATITUDE_CLAUDE_CODE_ENABLED | no | 1 | Set to 0 to pause the hook without uninstalling. | | LATITUDE_DEBUG | no | — | Set to 1 to log diagnostics to stderr. |

hooks.Stop

A single hook entry running npx -y @latitude-data/claude-code-telemetry after each turn (async).

Files on disk

  • ~/.claude/state/latitude/intercept.js — the preload shim BUN_OPTIONS points at. Written on every platform.
  • ~/.claude/state/latitude/state.json — per-session bookkeeping (transcript offsets, turn counts) so each hook invocation only processes new lines.
  • ~/.claude/state/latitude/requests/<message_id>.json — request bodies captured by the preload. Transient: consumed by the Stop hook and deleted after each turn.

macOS-only files

  • ~/Library/LaunchAgents/so.latitude.claude-code-telemetry.plist — a one-shot launchd agent that runs launchctl setenv BUN_OPTIONS … on every login. Only installed when you accept the launchctl prompt.

Manual installation

If the installer doesn't fit your setup (e.g. you manage dotfiles with another tool), the equivalent settings.json is:

{
  "env": {
    "LATITUDE_API_KEY": "lat_xxx",
    "LATITUDE_PROJECT": "my-project-slug"
  },
  "hooks": {
    "Stop": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "npx -y @latitude-data/claude-code-telemetry",
            "async": true
          }
        ]
      }
    ]
  }
}

You still need BUN_OPTIONS=--preload=<abs-path>/intercept.js in the claude runtime's environment to get the full-request capture. Either run install once to materialize the preload and set things up, or copy the shim out of node_modules/@latitude-data/claude-code-telemetry/dist/intercept.js and wire it up yourself.

Privacy

This hook reads your session transcript from disk and sends the full content to Latitude — prompts, assistant responses, tool I/O (including file contents returned by Read, Bash output, etc.). With the preload installed, it also sends Claude Code's system prompt and the list of tool schemas available to the model. There is no per-flag opt-in: the moment the hook is installed, everything gets shipped.

If that's not what you want, either:

  • Don't install the hook.
  • Set LATITUDE_CLAUDE_CODE_ENABLED=0 in your shell before starting a sensitive session.
  • Run uninstall.

Supported surfaces

| Surface | Works | | --- | --- | | CLI (claude in terminal) | ✅ | | Desktop app (macOS / Windows) | ✅ (uses launchctl on macOS) | | IDE extensions (VS Code, JetBrains) | ✅ | | Web app (claude.ai/code) | ❌ — runs in Anthropic's cloud; no local hooks. |

Caveats

  • The claude CLI is a Bun-compiled standalone. The preload relies on Bun honoring BUN_OPTIONS=--preload=... (verified against 2.1.x). If a future release changes this, the hook falls back to transcript-only reconstruction automatically — spans still work, they just lose gen_ai.system_instructions and gen_ai.tool.definitions.
  • If BUN_OPTIONS points at a missing preload file, claude itself will refuse to start (Bun exits 1). Either keep the file in place or run uninstall / remove the env var.
  • Captured request bodies are big (50–150KB each). Steady-state disk use is small because the Stop hook prunes them, but long sessions between Stop events can accumulate briefly.

How it fails

Fail-open by design. If the API is unreachable, your key is wrong, or the transcript is malformed, the hook logs to stderr (when LATITUDE_DEBUG=1) and exits 0. It never blocks Claude from finishing a turn.

License

MIT